From 34be7abc05d57fd4dd99116131e3ad52b7f05f91 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Tue, 29 Jan 2019 15:49:14 +0000 Subject: [PATCH 001/760] Support class components in story snapshot tests. --- __tests__/snapshots.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/__tests__/snapshots.test.js b/__tests__/snapshots.test.js index c802a987c..e32e029a7 100644 --- a/__tests__/snapshots.test.js +++ b/__tests__/snapshots.test.js @@ -2,6 +2,7 @@ const renderer = require('react-test-renderer'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); +const { h } = require('../packages/x-engine'); const {packages} = require('../monorepo.json'); @@ -16,16 +17,16 @@ for(const pkg of packageDirs) { const storiesDir = path.resolve(pkgDir, 'stories'); if(fs.existsSync(storiesDir)) { - const { package: pkg, stories, component } = require(storiesDir); - const { presets = {default: {}} } = require(pkgDir); + const { package: pkg, stories, component: Component } = require(storiesDir); + const { presets = { default: {} } } = require(pkgDir); const name = path.basename(pkg.name); describe(pkg.name, () => { for (const { title, data } of stories) { - for (const [ preset, options ] of Object.entries(presets)) { + for (const [preset, options] of Object.entries(presets)) { it(`renders a ${preset} ${title} ${name}`, () => { const props = { ...data, ...options }; - const tree = renderer.create(component(props)).toJSON(); + const tree = renderer.create(h(Component, props)).toJSON(); expect(tree).toMatchSnapshot(); }); } From ec420be3d1b76331525d30cbab1f8e6473c900e8 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Tue, 29 Jan 2019 15:58:47 +0000 Subject: [PATCH 002/760] Don't lint the x-docs. --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index bf82b7a02..c29184fe1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,3 +6,4 @@ **/public/** **/public-prod/** **/blueprints/** +tools/x-docs/static/** \ No newline at end of file From 9eb471a884c9228108363227f1be4a65781296c1 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Tue, 5 Feb 2019 12:03:33 +0000 Subject: [PATCH 003/760] No need to capitalise when not using JSX. --- __tests__/snapshots.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/snapshots.test.js b/__tests__/snapshots.test.js index e32e029a7..3f0abbd37 100644 --- a/__tests__/snapshots.test.js +++ b/__tests__/snapshots.test.js @@ -17,7 +17,7 @@ for(const pkg of packageDirs) { const storiesDir = path.resolve(pkgDir, 'stories'); if(fs.existsSync(storiesDir)) { - const { package: pkg, stories, component: Component } = require(storiesDir); + const { package: pkg, stories, component } = require(storiesDir); const { presets = { default: {} } } = require(pkgDir); const name = path.basename(pkg.name); @@ -26,7 +26,7 @@ for(const pkg of packageDirs) { for (const [preset, options] of Object.entries(presets)) { it(`renders a ${preset} ${title} ${name}`, () => { const props = { ...data, ...options }; - const tree = renderer.create(h(Component, props)).toJSON(); + const tree = renderer.create(h(component, props)).toJSON(); expect(tree).toMatchSnapshot(); }); } From bc6bf75b8cd72cd7a0f2da9ad78c61f6490a437a Mon Sep 17 00:00:00 2001 From: bren Date: Tue, 5 Feb 2019 15:21:14 +0000 Subject: [PATCH 004/760] add test for #224 --- .../__tests__/x-interaction.test.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/components/x-interaction/__tests__/x-interaction.test.jsx b/components/x-interaction/__tests__/x-interaction.test.jsx index 2708f39ad..6fd01be10 100644 --- a/components/x-interaction/__tests__/x-interaction.test.jsx +++ b/components/x-interaction/__tests__/x-interaction.test.jsx @@ -218,6 +218,25 @@ describe('x-interaction', () => { target.find(Base).prop('bar') ).toBe(15); }); + + it('should pass changed outside props to state updaters', async () => { + const Base = () => null; + const Wrapped = withActions({ + foo: () => ({ bar }) => ({ bar: bar + 5 }), + })(Base); + + const target = mount(); + + target.setProps({ bar: 10 }); + target.update(); + + await target.find(Base).prop('actions').foo(); + target.update(); + + expect( + target.find(Base).prop('bar') + ).toBe(15); + }); describe('actionsRef', () => { it('should pass actions to actionsRef on mount and null on unmount', async () => { From 044f93b56f84e5fb8f996ae2bb3e599b16865d64 Mon Sep 17 00:00:00 2001 From: bren Date: Tue, 5 Feb 2019 15:55:55 +0000 Subject: [PATCH 005/760] fix updated props in state updaters - regenerate wrapped actions when receiving props - fixes #224 --- components/x-interaction/src/InteractionClass.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/x-interaction/src/InteractionClass.jsx b/components/x-interaction/src/InteractionClass.jsx index a5288fd93..bffa82a90 100644 --- a/components/x-interaction/src/InteractionClass.jsx +++ b/components/x-interaction/src/InteractionClass.jsx @@ -11,6 +11,10 @@ export class InteractionClass extends Component { inFlight: 0, }; + this.createActions(props); + } + + createActions(props) { this.actions = mapValues(props.actions, (func) => async (...args) => { // mark as loading one microtask later. if the action is synchronous then // setting loading back to false will happen in the same microtask and no @@ -40,6 +44,10 @@ export class InteractionClass extends Component { }); } + componentWillReceiveProps(props) { + this.createActions(props); + } + componentDidMount() { if(this.props.actionsRef) { this.props.actionsRef(this.actions); From 0daff55ad99c399dff98733ce256a04afca92f64 Mon Sep 17 00:00:00 2001 From: chee Date: Thu, 7 Mar 2019 23:36:51 +0000 Subject: [PATCH 006/760] remove alt attr we probably don't need the screenreader to say "x-dash" twice --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index dcaad73a0..4129d8074 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@

- x-dash
+
x-dash Build Status From e6ffbf62d86d49b7d78eaed9decd30f581a09d44 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 15 Jan 2019 12:53:41 +0000 Subject: [PATCH 007/760] Add renovate.json --- renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..f45d8f110 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +} From 1b377f7cb6c400063723cedafa49f861c0294a88 Mon Sep 17 00:00:00 2001 From: Samuel Parkinson <51677+sjparkinson@users.noreply.github.com> Date: Mon, 19 Nov 2018 12:10:31 +0000 Subject: [PATCH 008/760] =?UTF-8?q?=F0=9F=A4=96=20Add=20Renovate=20configu?= =?UTF-8?q?ration=20to=20automatically=20manage=20dependencies.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Configure this repository to use our shared configuration defined at https://github.com/Financial-Times/renovate-config-next. Find more information on Renovate see https://github.com/Financial-Times/next/wiki/Renovate. --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index f45d8f110..639444f58 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,5 @@ { "extends": [ - "config:base" + "github>financial-times/renovate-config-next" ] } From 57387845b46d6ae6fc6aa3029a75377428ae10f9 Mon Sep 17 00:00:00 2001 From: Samuel Parkinson Date: Mon, 1 Apr 2019 09:55:58 +0100 Subject: [PATCH 009/760] chore(deps): upgrade to babel-jest v24 The current version includes a vulnerable version of the `braces` dependency. https://app.snyk.io/vuln/npm:braces:20180219 --- packages/x-babel-config/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-babel-config/package.json b/packages/x-babel-config/package.json index 9feadcaf4..14eda8948 100644 --- a/packages/x-babel-config/package.json +++ b/packages/x-babel-config/package.json @@ -11,7 +11,7 @@ "author": "", "license": "ISC", "dependencies": { - "babel-jest": "^23.6.0", + "babel-jest": "^24.0.0", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-react-jsx": "^6.24.1", "babel-preset-env": "^1.7.0", From b408e9934a6b20ee2a3b3d7d3c6cd87fe6ae7206 Mon Sep 17 00:00:00 2001 From: bren Date: Thu, 18 Apr 2019 12:07:49 +0100 Subject: [PATCH 010/760] update everything babel-related or is that @babel/related hahaha --- package.json | 6 ++++-- packages/x-babel-config/index.js | 12 ++---------- packages/x-babel-config/package.json | 7 +++---- packages/x-rollup/package.json | 6 +++--- packages/x-rollup/src/babel-config.js | 2 +- tools/x-ssr-demo/package.json | 3 ++- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index e16ae4e2d..d1f5b1608 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "eslint-plugin-react": "^7.10.0", "espree": "^4.1.0", "fetch-mock": "^6.5.2", - "jest": "^22.4.3", "react": "^16.3.1", "react-test-renderer": "^16.3.1" }, @@ -39,5 +38,8 @@ "components/*", "packages/*", "tools/*" - ] + ], + "devDependencies": { + "jest": "^24.7.1" + } } diff --git a/packages/x-babel-config/index.js b/packages/x-babel-config/index.js index 8082bd810..c02bb8186 100644 --- a/packages/x-babel-config/index.js +++ b/packages/x-babel-config/index.js @@ -2,20 +2,12 @@ module.exports = (targets = []) => ({ plugins: [ // this plugin is not React specific! It includes a general JSX parser and helper 🙄 [ - require.resolve('babel-plugin-transform-react-jsx'), + require.resolve('@babel/plugin-transform-react-jsx'), { pragma: 'h', useBuiltIns: true } ], - // Although this feature is at stage 4, we'd have to use babel 7 to get the version - // of preset-env that actually supports it 😖 - [ - require.resolve('babel-plugin-transform-object-rest-spread'), - { - useBuiltIns: true - } - ], // Implements async/await using syntax transformation rather than generators which require // a huge runtime for browsers which do not natively support them. [ @@ -29,7 +21,7 @@ module.exports = (targets = []) => ({ ], presets: [ [ - require.resolve('babel-preset-env'), + require.resolve('@babel/preset-env'), { targets, modules: false, diff --git a/packages/x-babel-config/package.json b/packages/x-babel-config/package.json index 14eda8948..12871ce10 100644 --- a/packages/x-babel-config/package.json +++ b/packages/x-babel-config/package.json @@ -11,10 +11,9 @@ "author": "", "license": "ISC", "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.3.0", + "@babel/preset-env": "^7.4.3", "babel-jest": "^24.0.0", - "babel-plugin-transform-object-rest-spread": "^6.26.0", - "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-preset-env": "^1.7.0", - "fast-async": "^6.3.7" + "fast-async": "^7.0.6" } } diff --git a/packages/x-rollup/package.json b/packages/x-rollup/package.json index 662a19751..97550a355 100644 --- a/packages/x-rollup/package.json +++ b/packages/x-rollup/package.json @@ -11,13 +11,13 @@ "author": "", "license": "ISC", "dependencies": { + "@babel/core": "^7.4.3", + "@babel/plugin-external-helpers": "^7.2.0", "@financial-times/x-babel-config": "file:../x-babel-config", - "babel-core": "^6.26.3", - "babel-plugin-external-helpers": "^6.22.0", "chalk": "^2.4.1", "log-symbols": "^2.2.0", "rollup": "^0.63.0", - "rollup-plugin-babel": "^3.0.7", + "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "^9.1.3", "rollup-plugin-postcss": "^1.6.2" } diff --git a/packages/x-rollup/src/babel-config.js b/packages/x-rollup/src/babel-config.js index aaf9ffa28..93f51c766 100644 --- a/packages/x-rollup/src/babel-config.js +++ b/packages/x-rollup/src/babel-config.js @@ -5,7 +5,7 @@ module.exports = (...args) => { base.plugins.push( // Instruct Babel to not include any internal helper declarations in the output - require.resolve('babel-plugin-external-helpers'), + require.resolve('@babel/plugin-external-helpers'), ); // rollup-specific option not included in base config diff --git a/tools/x-ssr-demo/package.json b/tools/x-ssr-demo/package.json index 78fbe0dd3..1dca25188 100644 --- a/tools/x-ssr-demo/package.json +++ b/tools/x-ssr-demo/package.json @@ -11,6 +11,7 @@ "author": "", "license": "ISC", "dependencies": { + "@babel/core": "^7.4.3", "@financial-times/n-handlebars": "^1.21.0", "@financial-times/x-babel-config": "file:../../packages/x-babel-config", "@financial-times/x-engine": "file:../../packages/x-engine", @@ -18,7 +19,7 @@ "@financial-times/x-increment": "file:../../components/x-increment", "@financial-times/x-interaction": "file:../../components/x-interaction", "babel-core": "^6.0.0", - "babel-loader": "^7.0.0", + "babel-loader": "^8.0.5", "express": "^4.16.3", "hyperons": "^0.5.0", "preact": "^8.2.9", From 011f494489908a56d8e314117cc74abe664cfa92 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Thu, 18 Apr 2019 14:59:02 +0100 Subject: [PATCH 011/760] Fix prerelease workflow job by ensuring package and version variables are saved between tasks --- .circleci/config.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b9be963cc..129b2d674 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -153,14 +153,18 @@ jobs: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - run: - name: Bump version number + name: Extract tag name and version number command: | # https://circleci.com/docs/2.0/env-vars/#using-bash_env-to-set-environment-variables export PRERELEASE_SEMVER='v0\.[0-9]{1,2}\.[0-9]{1,2}(-[a-z]+\.[0-9])?' export TARGET_VERSION=$(echo $CIRCLE_TAG | grep -o -E $PRERELEASE_SEMVER); export TARGET_MODULE=$(echo $CIRCLE_TAG | sed -E "s/-${PRERELEASE_SEMVER}//g"); + echo "export TARGET_VERSION=$TARGET_VERSION" >> $BASH_ENV; + echo "export TARGET_MODULE=$TARGET_MODULE" >> $BASH_ENV; echo "Creating prerelease ${TARGET_VERSION} for ${TARGET_MODULE}"; - npx athloi -F ${TARGET_MODULE} version ${TARGET_VERSION}; + - run: + name: Bump version number + command: npx athloi -F ${TARGET_MODULE} version ${TARGET_VERSION}; - run: name: NPM publish command: npx athloi -F ${TARGET_MODULE} publish -- --access=public --tag=pre-release From 66b66bdc064224c3363cc23bdd0172a2c5d094f0 Mon Sep 17 00:00:00 2001 From: Dan Searle Date: Wed, 17 Oct 2018 15:38:38 +0100 Subject: [PATCH 012/760] x-teaser-timeline (renamed and squashed from x-timeline-feed branch) --- .gitignore | 1 + .../__snapshots__/snapshots.test.js.snap | 4083 ++++++ components/x-teaser-timeline/.bowerrc | 8 + .../__tests__/TeaserTimeline.test.jsx | 115 + .../TeaserTimeline.test.jsx.snap | 11225 ++++++++++++++++ components/x-teaser-timeline/bower.json | 10 + components/x-teaser-timeline/package.json | 43 + components/x-teaser-timeline/readme.md | 56 + components/x-teaser-timeline/rollup.js | 4 + .../x-teaser-timeline/src/TeaserTimeline.jsx | 41 + .../x-teaser-timeline/src/TeaserTimeline.scss | 51 + components/x-teaser-timeline/src/lib/date.js | 49 + .../x-teaser-timeline/src/lib/transform.js | 83 + .../stories/content-items.json | 1040 ++ components/x-teaser-timeline/stories/index.js | 17 + .../stories/with-latest-items.js | 13 + .../stories/without-latest-items.js | 12 + tools/x-storybook/package.json | 1 + tools/x-storybook/register-components.js | 1 + 19 files changed, 16853 insertions(+) create mode 100644 components/x-teaser-timeline/.bowerrc create mode 100644 components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx create mode 100644 components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap create mode 100644 components/x-teaser-timeline/bower.json create mode 100644 components/x-teaser-timeline/package.json create mode 100644 components/x-teaser-timeline/readme.md create mode 100644 components/x-teaser-timeline/rollup.js create mode 100644 components/x-teaser-timeline/src/TeaserTimeline.jsx create mode 100644 components/x-teaser-timeline/src/TeaserTimeline.scss create mode 100644 components/x-teaser-timeline/src/lib/date.js create mode 100644 components/x-teaser-timeline/src/lib/transform.js create mode 100644 components/x-teaser-timeline/stories/content-items.json create mode 100644 components/x-teaser-timeline/stories/index.js create mode 100644 components/x-teaser-timeline/stories/with-latest-items.js create mode 100644 components/x-teaser-timeline/stories/without-latest-items.js diff --git a/.gitignore b/.gitignore index aed1a305d..c0be6db50 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ bower_components npm-debug.log .DS_Store dist +.idea \ No newline at end of file diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 2248d5399..aaae53a66 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -5948,3 +5948,4086 @@ exports[`@financial-times/x-teaser renders a TopStoryLandscape Video x-teaser 1` `; + +exports[`@financial-times/x-teaser-timeline renders a default With latest items x-teaser-timeline 1`] = ` +
+
+

+ Latest News +

+
+
+
+

+ Earlier Today +

+ +
+
+

+ Yesterday +

+ +
+
+

+ October 15, 2018 +

+ +
+
+`; + +exports[`@financial-times/x-teaser-timeline renders a default Without latest items x-teaser-timeline 1`] = ` +
+
+

+ Earlier Today +

+ +
+
+

+ Yesterday +

+ +
+
+

+ October 15, 2018 +

+ +
+
+`; diff --git a/components/x-teaser-timeline/.bowerrc b/components/x-teaser-timeline/.bowerrc new file mode 100644 index 000000000..b540c6fdb --- /dev/null +++ b/components/x-teaser-timeline/.bowerrc @@ -0,0 +1,8 @@ +{ + "registry": { + "search": [ + "https://origami-bower-registry.ft.com", + "https://registry.bower.io" + ] + } +} \ No newline at end of file diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx new file mode 100644 index 000000000..e0fe1c9c1 --- /dev/null +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -0,0 +1,115 @@ +const renderer = require('react-test-renderer'); +const { h } = require('@financial-times/x-engine'); +const { mount } = require('@financial-times/x-test-utils/enzyme'); +const contentItems = require('../stories/content-items.json'); + +const { TeaserTimeline } = require('../'); + +describe('x-teaser-timeline', () => { + let props; + let tree; + + beforeEach(() => { + props = { + items: contentItems, + timezoneOffset: -60, + localTodayDate: '2018-10-17' + }; + }); + + describe('given latestItemsTime is set', () => { + beforeEach(() => { + tree = renderer.create().toJSON(); + }); + + it('renders latest, earlier, yesterday and October 15th item groups', () => { + expect(tree).toMatchSnapshot(); + }); + }); + + describe('given latestItemsTime is not set', () => { + beforeEach(() => { + tree = renderer.create().toJSON(); + }); + + it('renders earlier, yesterday and October 15th item groups (no latest)', () => { + expect(tree).toMatchSnapshot(); + }); + }); + + describe('given latestItemsTime is set but is not same date as localTodayDate', () => { + beforeEach(() => { + tree = renderer.create().toJSON(); + }); + + it('ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest)', () => { + expect(tree).toMatchSnapshot(); + }); + }); + + describe('itemCustomSlot', () => { + let mockItemActionsCreator; + + describe('given itemCustomSlot is a function', () => { + beforeEach(() => { + mockItemActionsCreator = jest.fn(item => `action for ${item.id}`); + tree = renderer.create().toJSON(); + }); + + it('should call the itemCustomSlot for each item, with each item', () => { + mockItemActionsCreator.mock.calls.forEach((c, i) => { + expect(c[0]).toEqual(expect.objectContaining(contentItems[i])); + }); + }); + + it('renders each item with the created item-specific action', () => { + expect(tree).toMatchSnapshot(); + }); + }); + describe('given itemCustomSlot is a JSX child', () => { + beforeEach(() => { + mockItemActionsCreator = I am an action; + tree = renderer.create().toJSON(); + }); + + it('renders each item with the action', () => { + expect(tree).toMatchSnapshot(); + }); + }); + + describe('given itemCustomSlot is not set', () => { + beforeEach(() => { + tree = renderer.create().toJSON(); + }); + + it('renders each item without an action', () => { + expect(tree).toMatchSnapshot(); + }); + }); + }); + + describe('given no item are provided', () => { + let component; + + beforeEach(() => { + delete props.items; + component = mount(); + }); + + it('should render nothing', () => { + expect(component.html()).toEqual(null); + }) + }); +}); diff --git a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap new file mode 100644 index 000000000..ca1d75940 --- /dev/null +++ b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap @@ -0,0 +1,11225 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yesterday and October 15th item groups (no latest) 1`] = ` +
+
+

+ Earlier Today +

+ +
+
+

+ Yesterday +

+ +
+
+

+ October 15, 2018 +

+ +
+
+`; + +exports[`x-teaser-timeline given latestItemsTime is set but is not same date as localTodayDate ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest) 1`] = ` +
+
+

+ Earlier Today +

+ +
+
+

+ Yesterday +

+ +
+
+

+ October 15, 2018 +

+ +
+
+`; + +exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, yesterday and October 15th item groups 1`] = ` +
+
+

+ Latest News +

+ +
+
+

+ Earlier Today +

+ +
+
+

+ Yesterday +

+ +
+
+

+ October 15, 2018 +

+ +
+
+`; + +exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is a JSX child renders each item with the action 1`] = ` +
+
+

+ Earlier Today +

+ +
+
+

+ Yesterday +

+ +
+
+

+ October 15, 2018 +

+ +
+
+`; + +exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is a function renders each item with the created item-specific action 1`] = ` +
+
+

+ Earlier Today +

+ +
+
+

+ Yesterday +

+ +
+
+

+ October 15, 2018 +

+ +
+
+`; + +exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is not set renders each item without an action 1`] = ` +
+
+

+ Earlier Today +

+ +
+
+

+ Yesterday +

+ +
+
+

+ October 15, 2018 +

+ +
+
+`; diff --git a/components/x-teaser-timeline/bower.json b/components/x-teaser-timeline/bower.json new file mode 100644 index 000000000..65ae12d90 --- /dev/null +++ b/components/x-teaser-timeline/bower.json @@ -0,0 +1,10 @@ +{ + "name": "x-teaser-timeline", + "private": true, + "main": "dist/TeaserTimeline.es5.js", + "dependencies": { + "o-colors": "^4.7.2", + "o-grid": "4.4.4", + "o-typography": "^5.7.5" + } +} diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json new file mode 100644 index 000000000..f74fa5bb4 --- /dev/null +++ b/components/x-teaser-timeline/package.json @@ -0,0 +1,43 @@ +{ + "name": "@financial-times/x-teaser-timeline", + "version": "0.0.0", + "description": "", + "main": "dist/TeaserTimeline.cjs.js", + "module": "dist/TeaserTimeline.esm.js", + "browser": "dist/TeaserTimeline.es5.js", + "style": "dist/TeaserTimeline.css", + "scripts": { + "prepare": "npm run build", + "build": "node rollup.js", + "start": "node rollup.js --watch", + "postinstall": "bower install" + }, + "keywords": [ + "x-dash" + ], + "author": "", + "license": "ISC", + "dependencies": { + "@financial-times/x-engine": "file:../../packages/x-engine", + "@financial-times/x-teaser": "file:../x-teaser", + "classnames": "^2.2.6", + "date-fns": "^1.29.0" + }, + "devDependencies": { + "@financial-times/x-rollup": "file:../../packages/x-rollup", + "@financial-times/x-test-utils": "file:../../packages/x-test-utils", + "node-sass": "^4.9.2", + "bower": "^1.7.9" + }, + "repository": { + "type": "git", + "url": "https://github.com/Financial-Times/x-dash.git" + }, + "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-teaser-timeline", + "engines": { + "node": ">= 6.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md new file mode 100644 index 000000000..261af9864 --- /dev/null +++ b/components/x-teaser-timeline/readme.md @@ -0,0 +1,56 @@ +# x-teaser-timeline + +This component renders a list of articles in reverse chronological order, grouped by day, according to the user's timezone. +It will optionally group today's articles into "latest" and "earlier" too. + +## Installation + +This module is compatible with Node 6+ and is distributed on npm. + +```bash +npm install --save @financial-times/x-teaser-timeline +``` + +The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. + +[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine + + +## Usage + +The components provided by this module are all functions that expect a map of [properties](#properties). They can be used with vanilla JavaScript or JSX (If you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: + +```jsx +import React from 'react'; +import { TeaserTimeline } from '@financial-times/x-teaser-timeline'; + +// A == B == C +const a = TeaserTimeline(props); +const b = ; +const c = React.createElement(TeaserTimeline, props); +``` + +All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. + +[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ + +### Properties + +Feature | Type | Notes +---------------------|-----------------|---------------------------- +`items` | Array | (Mandatory) Array of objects, in Teaser format, representating content items to render. The items should be in newest-first order. +`timezoneOffset` | Number | (Defaults using runtime clock) Minutes to offset item publish times in order to display in user's timezone. Negative means ahead of UTC. +`localTodayDate` | String | (Defaults using runtime clock) ISO format YYYY-MM-DD representating today's date in the user's timezone. +`latestItemsTime` | String | ISO time (HH:mm:ss). If provided, will be used in combination with `localTodayDate` to render today's items into separate "Latest" and "Earlier" groups +`itemCustomSlot` | JSX or function | A JSX child or function for rendering custom content for each item. This is likely to be for things like an x-article-save-button. + +Example: + +```jsx + +``` \ No newline at end of file diff --git a/components/x-teaser-timeline/rollup.js b/components/x-teaser-timeline/rollup.js new file mode 100644 index 000000000..1b358fef1 --- /dev/null +++ b/components/x-teaser-timeline/rollup.js @@ -0,0 +1,4 @@ +const xRollup = require('@financial-times/x-rollup'); +const pkg = require('./package.json'); + +xRollup({ input: './src/TeaserTimeline.jsx', pkg }); diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx new file mode 100644 index 000000000..69b9f32c5 --- /dev/null +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -0,0 +1,41 @@ +import { h } from '@financial-times/x-engine'; +import { Teaser, presets } from '@financial-times/x-teaser'; +import { getItemGroups } from './lib/transform'; +import styles from './TeaserTimeline.scss'; +import classNames from 'classnames'; + +const TeaserTimeline = props => { + const { itemCustomSlot = () => {} } = props; + const itemGroups = getItemGroups(props); + + return itemGroups.length && ( +
+ {itemGroups.map(group => ( +
+

{group.title}

+
    + {group.items.map(item => { + const slotContent = typeof itemCustomSlot === 'function' ? itemCustomSlot(item): itemCustomSlot; + + return ( +
  • + + {typeof slotContent === 'string' ?
    : slotContent} +
  • + ); + })} +
+
+ ))} +
+ ); +}; + +export { TeaserTimeline }; diff --git a/components/x-teaser-timeline/src/TeaserTimeline.scss b/components/x-teaser-timeline/src/TeaserTimeline.scss new file mode 100644 index 000000000..8b5d300bd --- /dev/null +++ b/components/x-teaser-timeline/src/TeaserTimeline.scss @@ -0,0 +1,51 @@ +@import 'o-colors/main'; +@import 'o-grid/main'; +@import 'o-typography/main'; + +.itemGroup { + border-top: 4px solid #000; + + @include oGridRespondTo($from: M) { + display: grid; + grid-gap: 0 20px; + grid-template: "heading articles" min-content / 1fr 3fr; + } +} + +.itemGroup__heading { + @include oTypographySansBold($scale: 1); + @include oTypographyMargin($top: 2, $bottom: 2); + + @include oGridRespondTo($from: M) { + grid-area: heading; + } +} + +.itemGroup__items { + list-style-type: none; + padding: 0; + @include oTypographyMargin($top: 2); + + @include oGridRespondTo($from: M) { + grid-area: articles; + } +} + +.item { + display: flex; + justify-content: space-between; + border-bottom: 1px solid oColorsGetPaletteColor('black-20'); + @include oTypographyMargin($bottom: 2); + + :global { + .o-teaser--timeline-teaser { + border-bottom: 0; + @include oTypographyPadding($bottom: 0); + } + } +} + +.itemActions { + flex: 0 1 auto; + padding-left: 10px; +} diff --git a/components/x-teaser-timeline/src/lib/date.js b/components/x-teaser-timeline/src/lib/date.js new file mode 100644 index 000000000..33c9a4309 --- /dev/null +++ b/components/x-teaser-timeline/src/lib/date.js @@ -0,0 +1,49 @@ +import { format, isAfter, differenceInCalendarDays, subMinutes } from 'date-fns'; + +/** + * Takes a UTC ISO date/time and turns it into a ISO date for a particular timezone + * @param {string} isoDate A UTC ISO date, e.g. '2018-07-19T12:00:00.000Z' + * @param {number} timezoneOffset Minutes ahead (negative) or behind UTC + * @return {string} A localised ISO date, e.g. '2018-07-19T00:30:00.000+01:00' for UTC+1 + */ +export const getLocalisedISODate = (isoDate, timezoneOffset) => { + const dateWithoutTimezone = subMinutes(isoDate, timezoneOffset).toISOString().substring(0, 23); + const future = timezoneOffset <= 0; + const offsetMinutes = Math.abs(timezoneOffset); + const hours = Math.floor(offsetMinutes / 60); + const minutes = offsetMinutes % 60; + const pad = n => String(n).padStart(2, '0'); + + return `${dateWithoutTimezone}${future ? '+' : '-'}${pad(hours)}:${pad(minutes)}`; +}; + +export const getTitleForItemGroup = (localDate, localTodayDate) => { + if (localDate === 'today-latest') { + return 'Latest News' + } + + if (localDate === 'today-earlier' || localDate === localTodayDate) { + return 'Earlier Today'; + } + + if (differenceInCalendarDays(localTodayDate, localDate) === 1) { + return 'Yesterday'; + } + + return format(localDate, 'MMMM D, YYYY'); +}; + +export const splitLatestEarlier = (items, splitDate) => { + const latestItems = []; + const earlierItems = []; + + items.forEach(item => { + if (isAfter(item.localisedLastUpdated, splitDate)) { + latestItems.push(item); + } else { + earlierItems.push(item); + } + }); + + return { latestItems, earlierItems }; +}; diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js new file mode 100644 index 000000000..bf18eda56 --- /dev/null +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -0,0 +1,83 @@ +import { + getLocalisedISODate, + getTitleForItemGroup, + splitLatestEarlier +} from './date'; + +export const getDateOnly = date => date.substr(0, 10); + +export const groupItemsByLocalisedDate = (items, timezoneOffset) => { + const itemsByLocalisedDate = {}; + + items.forEach((item, index) => { + const localDateTime = getLocalisedISODate(item.publishedDate, timezoneOffset); + const localDate = getDateOnly(localDateTime); + + if (!itemsByLocalisedDate.hasOwnProperty(localDate)) { + itemsByLocalisedDate[localDate] = []; + } + + item.localisedLastUpdated = localDateTime; + itemsByLocalisedDate[localDate].push({ articleIndex: index, ...item }); + }); + + return Object.entries(itemsByLocalisedDate).map(([localDate, items]) => ({ + date: localDate, + items + })); +}; + +export const splitTodaysItems = (itemGroups, localTodayDate, latestItemsTime) => { + const firstGroupIsToday = itemGroups[0] && itemGroups[0].date === localTodayDate; + const latestTimeIsToday = getDateOnly(latestItemsTime) === localTodayDate; + + if (!firstGroupIsToday || !latestTimeIsToday) { + return itemGroups; + } + + const { latestItems, earlierItems } = splitLatestEarlier(itemGroups[0].items, latestItemsTime); + + itemGroups[0] = { + date: 'today-earlier', + items: earlierItems + }; + + if (latestItems.length) { + itemGroups.unshift({ + date: 'today-latest', + items: latestItems + }); + } + + return itemGroups; +}; + +export const addItemGroupTitles = (itemGroups, localTodayDate) => { + return itemGroups.map(group => { + group.title = getTitleForItemGroup(group.date, localTodayDate); + + return group; + }); +}; + +export const getItemGroups = props => { + const now = new Date(); + const { + items, + timezoneOffset = now.getTimezoneOffset(), + localTodayDate = getDateOnly(now.toISOString()), + latestItemsTime + } = props; + + if (!items || !Array.isArray(items) || items.length === 0) { + return []; + } + + let itemGroups = groupItemsByLocalisedDate(items, timezoneOffset); + + if (latestItemsTime) { + itemGroups = splitTodaysItems(itemGroups, localTodayDate, latestItemsTime); + } + + return addItemGroupTitles(itemGroups, localTodayDate); +}; diff --git a/components/x-teaser-timeline/stories/content-items.json b/components/x-teaser-timeline/stories/content-items.json new file mode 100644 index 000000000..9b0adcbed --- /dev/null +++ b/components/x-teaser-timeline/stories/content-items.json @@ -0,0 +1,1040 @@ +[ + { + "id": "65867e26-d203-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/65867e26-d203-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/65867e26-d203-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "464cc2f2-395e-4c36-bb29-01727fc95558", + "predicate": "http://www.ft.com/ontology/classification/isClassifiedBy", + "prefLabel": "Brexit Briefing", + "type": "BRAND", + "url": "https://www.ft.com/brexit-briefing", + "relativeUrl": "/brexit-briefing" + }, + "metaAltLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit", + "isDisplayTag": true + }, + "title": "Is December looming as the new Brexit deadline?", + "standfirst": "As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely", + "altStandfirst": null, + "publishedDate": "2018-10-17T13:00:26.000Z", + "firstPublishedDate": "2018-10-17T13:00:26.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/c8a8d52e-d20a-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "93614586-d1f1-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/93614586-d1f1-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/93614586-d1f1-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "May seeks to overcome deadlock after Brexit setback", + "standfirst": "UK and EU consider extending transition deal to defuse dispute over Irish border backstop", + "altStandfirst": null, + "publishedDate": "2018-10-17T12:35:36.000Z", + "firstPublishedDate": "2018-10-17T12:35:36.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/8ba50178-d216-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "d4e80114-d1ee-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "Midsized UK businesses turn sour on Brexit", + "standfirst": "Quarterly survey shows more companies now believe Brexit will damage their business than help them", + "altStandfirst": null, + "publishedDate": "2018-10-17T12:00:33.000Z", + "firstPublishedDate": "2018-10-17T12:00:33.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/a3695666-d201-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "6582b8ce-d175-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "Barnier open to extending Brexit transition by a year", + "standfirst": "In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border", + "altStandfirst": null, + "publishedDate": "2018-10-17T09:06:19.000Z", + "firstPublishedDate": "2018-10-16T19:45:43.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/9be47d9e-d17a-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "7ab52d68-d11a-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": true, + "isOpinion": true, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": "Inside Business", + "metaSuffixText": null, + "metaLink": { + "id": "3f97b34f-8fa3-43fd-8326-d8b0fde2a1f3", + "predicate": "http://www.ft.com/ontology/annotation/hasAuthor", + "prefLabel": "Sarah Gordon", + "type": "PERSON", + "attributes": [ + { + "key": "headshot", + "value": "fthead-v1:sarah-gordon" + } + ], + "url": "https://www.ft.com/sarah-gordon", + "relativeUrl": "/sarah-gordon" + }, + "metaAltLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit", + "isDisplayTag": true + }, + "title": "UK lets down business with lack of Brexit advice", + "standfirst": "Governments should be more concerned about SMEs: if supply chains falter, so will economic growth", + "altStandfirst": null, + "publishedDate": "2018-10-17T04:01:53.000Z", + "firstPublishedDate": "2018-10-17T04:01:53.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/23529cb0-d140-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": "fthead-v1:sarah-gordon", + "parentTheme": null + }, + { + "id": "868cedae-d18a-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/868cedae-d18a-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/868cedae-d18a-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "243243d9-de4b-4869-909b-fab711125624", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Global trade", + "type": "TOPIC", + "url": "https://www.ft.com/global-trade", + "relativeUrl": "/global-trade", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "Trump looks to start formal US-UK trade talks", + "standfirst": "White House makes London a priority ‘as soon as it is ready’ after Brexit", + "altStandfirst": null, + "publishedDate": "2018-10-16T23:31:16.000Z", + "firstPublishedDate": "2018-10-16T23:31:16.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/f08f8340-d1a0-11e8-a9f2-7574db66bcd5", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "c276c8a2-d159-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "fa354fe2-b2e0-483a-9267-cffe6ef9083b", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "House of Commons UK", + "type": "ORGANISATION", + "url": "https://www.ft.com/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b", + "relativeUrl": "/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "Bercow faces mounting calls to resign", + "standfirst": "Maria Miller says Speaker needs to step down immediately to ‘drive culture change’", + "altStandfirst": null, + "publishedDate": "2018-10-16T18:13:59.000Z", + "firstPublishedDate": "2018-10-16T17:12:31.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/ece474ca-d151-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "a1566658-d12e-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/a1566658-d12e-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/a1566658-d12e-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": true, + "isOpinion": true, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": "The FT View", + "metaSuffixText": null, + "metaLink": { + "id": "741cc381-76a8-382b-a621-fc8eca53d69f", + "predicate": "http://www.ft.com/ontology/annotation/hasAuthor", + "prefLabel": "The editorial board", + "type": "PERSON", + "url": "https://www.ft.com/ft-view", + "relativeUrl": "/ft-view" + }, + "metaAltLink": { + "id": "fa354fe2-b2e0-483a-9267-cffe6ef9083b", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "House of Commons UK", + "type": "ORGANISATION", + "url": "https://www.ft.com/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b", + "relativeUrl": "/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b", + "isDisplayTag": true + }, + "title": "A culture shift to clear Westminster’s toxic air", + "standfirst": "Speaker John Bercow should take responsibility — and go now", + "altStandfirst": null, + "publishedDate": "2018-10-16T17:36:20.000Z", + "firstPublishedDate": "2018-10-16T17:36:20.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/5319de7e-d12f-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "parentTheme": null + }, + { + "id": "f9f69a2c-d141-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "EU demands UK break Brexit impasse", + "standfirst": "May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’", + "altStandfirst": null, + "publishedDate": "2018-10-16T17:23:30.000Z", + "firstPublishedDate": "2018-10-16T13:07:03.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/62fd9e46-d14f-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "eb6062c2-d13c-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "464cc2f2-395e-4c36-bb29-01727fc95558", + "predicate": "http://www.ft.com/ontology/classification/isClassifiedBy", + "prefLabel": "Brexit Briefing", + "type": "BRAND", + "url": "https://www.ft.com/brexit-briefing", + "relativeUrl": "/brexit-briefing" + }, + "metaAltLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit", + "isDisplayTag": true + }, + "title": "Brexit, Scotland and the threat to constitutional unity", + "standfirst": "Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself", + "altStandfirst": null, + "publishedDate": "2018-10-16T13:54:09.000Z", + "firstPublishedDate": "2018-10-16T13:54:09.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/cb11f55e-d140-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "d7472b20-d148-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/d7472b20-d148-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/d7472b20-d148-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse", + "altStandfirst": null, + "publishedDate": "2018-10-16T13:49:36.000Z", + "firstPublishedDate": "2018-10-16T13:49:36.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/383a4ea2-d14a-11e8-a9f2-7574db66bcd5", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "10ee3a20-d13b-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "FIGI": "BBG000BWQFY7", + "id": "1120e446-6695-4e49-91e6-fd1f7698388e", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Wells Fargo", + "type": "ORGANISATION", + "url": "https://www.ft.com/stream/1120e446-6695-4e49-91e6-fd1f7698388e", + "relativeUrl": "/stream/1120e446-6695-4e49-91e6-fd1f7698388e", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "Wells Fargo applies for licence in France as part of Brexit strategy", + "altStandfirst": null, + "publishedDate": "2018-10-16T12:16:17.000Z", + "firstPublishedDate": "2018-10-16T12:16:17.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/31d2be9e-d13d-11e8-a9f2-7574db66bcd5", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "6bd7f80e-d11d-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "c4c9204f-8335-42c3-9415-c2c8cd971125", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "UK welfare reform", + "type": "TOPIC", + "url": "https://www.ft.com/stream/c4c9204f-8335-42c3-9415-c2c8cd971125", + "relativeUrl": "/stream/c4c9204f-8335-42c3-9415-c2c8cd971125", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "Rollout of controversial UK welfare reform faces fresh delay", + "standfirst": "Universal credit system not expected to be fully operational until December 2023", + "altStandfirst": null, + "publishedDate": "2018-10-16T12:03:13.000Z", + "firstPublishedDate": "2018-10-16T12:03:13.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/d7517de4-d131-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "1969887a-d11e-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/1969887a-d11e-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/1969887a-d11e-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "466a4700-307f-47cc-83f1-c5f97a172232", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "Pound Sterling", + "type": "TOPIC", + "url": "https://www.ft.com/stream/466a4700-307f-47cc-83f1-c5f97a172232", + "relativeUrl": "/stream/466a4700-307f-47cc-83f1-c5f97a172232", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "Pound rallies after upbeat wage growth reading", + "standfirst": "Analysts remain cautious despite optimistic data", + "altStandfirst": null, + "publishedDate": "2018-10-16T10:46:19.000Z", + "firstPublishedDate": "2018-10-16T09:18:12.000Z", + "image": {}, + "headshot": null, + "parentTheme": null + }, + { + "id": "dab8bfd2-ce3f-11e8-9fe5-24ad351828ab", + "url": "https://www.ft.com/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab", + "relativeUrl": "/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "9c0f3107-a34f-4319-a6a6-3101ac88b50d", + "predicate": "http://www.ft.com/ontology/hasDisplayTag", + "prefLabel": "UK politics & policy", + "type": "TOPIC", + "url": "https://www.ft.com/world/uk/politics", + "relativeUrl": "/world/uk/politics", + "isDisplayTag": true + }, + "metaAltLink": null, + "title": "Problem gambling shake-up set to be brought forward", + "standfirst": "Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits", + "altStandfirst": null, + "publishedDate": "2018-10-16T03:00:59.000Z", + "firstPublishedDate": "2018-10-16T03:00:59.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/a6afae18-d069-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "6b32f2c1-da43-4e19-80b9-8aef4ab640d7", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "Technology sector", + "type": "TOPIC", + "url": "https://www.ft.com/companies/technology", + "relativeUrl": "/companies/technology" + }, + "metaAltLink": null, + "title": "Crypto exchange Coinbase sets up Brexit contingency", + "standfirst": "US digital start-up to scale up EU operations with new Dublin branch", + "altStandfirst": null, + "publishedDate": "2018-10-15T23:01:36.000Z", + "firstPublishedDate": "2018-10-15T23:01:36.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/9a6adda6-d07a-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "2e407a74-d06a-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "82645c31-4426-4ef5-99c9-9df6e0940c00", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "World", + "type": "TOPIC", + "url": "https://www.ft.com/world", + "relativeUrl": "/world" + }, + "metaAltLink": null, + "title": "EU gives UK 24 hour Brexit breathing space", + "standfirst": "Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely", + "altStandfirst": null, + "publishedDate": "2018-10-15T18:13:59.000Z", + "firstPublishedDate": "2018-10-15T12:08:01.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/ac0fd098-d075-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "434dc8e2-d09f-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit" + }, + "metaAltLink": null, + "title": "EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’", + "altStandfirst": null, + "publishedDate": "2018-10-15T17:30:48.000Z", + "firstPublishedDate": "2018-10-15T17:30:48.000Z", + "image": {}, + "headshot": null, + "parentTheme": null + }, + { + "id": "fadfb212-d091-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/fadfb212-d091-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/fadfb212-d091-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit" + }, + "metaAltLink": null, + "title": "Barnier plan no solution to Irish border, says Foster", + "altStandfirst": null, + "publishedDate": "2018-10-15T16:06:36.000Z", + "firstPublishedDate": "2018-10-15T16:06:36.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/d0cbda02-cbb7-11e8-8d0b-a6539b949662", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "79939f94-c320-11e8-8d55-54197280d3f7", + "url": "https://www.ft.com/content/79939f94-c320-11e8-8d55-54197280d3f7", + "relativeUrl": "/content/79939f94-c320-11e8-8d55-54197280d3f7", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": "Explainer", + "metaSuffixText": null, + "metaLink": { + "id": "466a4700-307f-47cc-83f1-c5f97a172232", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "Pound Sterling", + "type": "TOPIC", + "url": "https://www.ft.com/stream/466a4700-307f-47cc-83f1-c5f97a172232", + "relativeUrl": "/stream/466a4700-307f-47cc-83f1-c5f97a172232" + }, + "metaAltLink": null, + "title": "Pound timeline: from the 1970s to Brexit crunch", + "standfirst": "Sterling has experienced several periods of volatility in the past 48 years", + "altStandfirst": null, + "publishedDate": "2018-10-15T13:44:20.000Z", + "firstPublishedDate": "2018-10-15T13:44:20.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/aea311c6-c32d-11e8-95b1-d36dfef1b89a", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "bfffa642-d079-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/bfffa642-d079-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/bfffa642-d079-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit" + }, + "metaAltLink": null, + "title": "Brexit talks could drag into December warns Ireland’s Varadkar", + "standfirst": "Taoiseach says he always believed a deal this month was unlikely", + "altStandfirst": null, + "publishedDate": "2018-10-15T13:05:06.000Z", + "firstPublishedDate": "2018-10-15T13:05:06.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/f87f782a-d089-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "82ae2756-d073-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/82ae2756-d073-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/82ae2756-d073-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "464cc2f2-395e-4c36-bb29-01727fc95558", + "predicate": "http://www.ft.com/ontology/classification/isClassifiedBy", + "prefLabel": "Brexit Briefing", + "type": "BRAND", + "url": "https://www.ft.com/brexit-briefing", + "relativeUrl": "/brexit-briefing" + }, + "metaAltLink": { + "id": "d7253b94-b248-4022-af1e-e99c15d0c1b6", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "UK trade", + "type": "TOPIC", + "url": "https://www.ft.com/uk-trade", + "relativeUrl": "/uk-trade" + }, + "title": "Crisis or choreography over Brexit?", + "standfirst": "While ministers haggle, company bosses press the button on expensive no-deal planning", + "altStandfirst": null, + "publishedDate": "2018-10-15T13:04:49.000Z", + "firstPublishedDate": "2018-10-15T13:04:49.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/31c1c612-d079-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "a8181102-ce1e-11e8-9fe5-24ad351828ab", + "url": "https://www.ft.com/content/a8181102-ce1e-11e8-9fe5-24ad351828ab", + "relativeUrl": "/content/a8181102-ce1e-11e8-9fe5-24ad351828ab", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "d1252624-8645-438b-b521-9244aebdc99e", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "Industrials", + "type": "TOPIC", + "url": "https://www.ft.com/companies/industrials", + "relativeUrl": "/companies/industrials" + }, + "metaAltLink": null, + "title": "UK shipyards to submit bids to build 5 warships", + "standfirst": "MoD restarts competition despite industry’s concerns over budget and timing", + "altStandfirst": null, + "publishedDate": "2018-10-15T11:02:36.000Z", + "firstPublishedDate": "2018-10-15T11:02:36.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/f1191270-ce41-11e8-8d0b-a6539b949662", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "9b3b6d2e-d064-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "19b95057-4614-45fb-9306-4d54049354db", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "Brexit", + "type": "TOPIC", + "url": "https://www.ft.com/brexit", + "relativeUrl": "/brexit" + }, + "metaAltLink": null, + "title": "May to address UK parliament on state of Brexit talks", + "altStandfirst": null, + "publishedDate": "2018-10-15T10:41:21.000Z", + "firstPublishedDate": "2018-10-15T10:41:21.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/c77408fa-d065-11e8-a9f2-7574db66bcd5", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "ed665ed8-cdfd-11e8-9fe5-24ad351828ab", + "url": "https://www.ft.com/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab", + "relativeUrl": "/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "6cc4c4ed-ef61-4c71-a5bb-f454fb5010a8", + "predicate": "http://www.ft.com/ontology/classification/isPrimarilyClassifiedBy", + "prefLabel": "UK business & economy", + "type": "TOPIC", + "url": "https://www.ft.com/uk-business-economy", + "relativeUrl": "/uk-business-economy" + }, + "metaAltLink": null, + "title": "Bradford hospital launches AI powered command centre", + "standfirst": "Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources", + "altStandfirst": null, + "publishedDate": "2018-10-15T10:01:47.000Z", + "firstPublishedDate": "2018-10-15T10:01:47.000Z", + "image": { + "url": "http://prod-upp-image-read.ft.com/66d87a12-d06f-11e8-9a3c-5d5eac8f1ab4", + "width": 2048, + "height": 1152 + }, + "headshot": null, + "parentTheme": null + }, + { + "id": "7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426", + "url": "http://ftalphaville.ft.com/marketslive/2018-10-15/", + "relativeUrl": "http://ftalphaville.ft.com/marketslive/2018-10-15/", + "type": "article", + "indicators": { + "isColumn": true, + "isOpinion": true, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "registered" + }, + "metaPrefixText": "FT Alphaville", + "metaSuffixText": null, + "metaLink": { + "id": "602c702b-31fe-4116-a203-747c7c737eb7", + "predicate": "http://www.ft.com/ontology/annotation/hasAuthor", + "prefLabel": "Bryce Elder", + "type": "PERSON", + "url": "https://www.ft.com/markets/bryce-elder", + "relativeUrl": "/markets/bryce-elder" + }, + "metaAltLink": { + "id": "c91b1fad-1097-468b-be82-9a8ff717d54c", + "predicate": "http://www.ft.com/ontology/classification/isPrimarilyClassifiedBy", + "prefLabel": "Markets", + "type": "TOPIC", + "url": "https://www.ft.com/markets", + "relativeUrl": "/markets" + }, + "title": "Markets Live: Monday, 15th October 2018", + "altStandfirst": null, + "publishedDate": "2018-10-15T10:01:26.000Z", + "firstPublishedDate": "2018-10-15T10:01:26.000Z", + "image": {}, + "parentTheme": null + }, + { + "id": "aa9b9bda-d056-11e8-a9f2-7574db66bcd5", + "url": "https://www.ft.com/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5", + "relativeUrl": "/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5", + "type": "article", + "indicators": { + "isColumn": false, + "isOpinion": false, + "isScoop": false, + "isExclusive": false, + "isEditorsChoice": false, + "accessLevel": "subscribed" + }, + "metaPrefixText": null, + "metaSuffixText": null, + "metaLink": { + "id": "d7253b94-b248-4022-af1e-e99c15d0c1b6", + "predicate": "http://www.ft.com/ontology/annotation/about", + "prefLabel": "UK trade", + "type": "TOPIC", + "url": "https://www.ft.com/uk-trade", + "relativeUrl": "/uk-trade" + }, + "metaAltLink": null, + "title": "Irish deputy PM voices ‘frustration’ at Brexit impasse", + "altStandfirst": null, + "publishedDate": "2018-10-15T08:50:32.000Z", + "firstPublishedDate": "2018-10-15T08:50:32.000Z", + "image": {}, + "headshot": null, + "parentTheme": null + } +] diff --git a/components/x-teaser-timeline/stories/index.js b/components/x-teaser-timeline/stories/index.js new file mode 100644 index 000000000..c3c07da70 --- /dev/null +++ b/components/x-teaser-timeline/stories/index.js @@ -0,0 +1,17 @@ +const { TeaserTimeline } = require('../'); + +exports.component = TeaserTimeline; + +exports.package = require('../package.json'); + +// Set up basic document styling using the Origami build service +exports.dependencies = { + 'o-normalise': '^1.6.0', + 'o-typography': '^5.5.0', + 'o-teaser': '^2.3.1' +}; + +exports.stories = [ + require('./without-latest-items'), + require('./with-latest-items'), +]; diff --git a/components/x-teaser-timeline/stories/with-latest-items.js b/components/x-teaser-timeline/stories/with-latest-items.js new file mode 100644 index 000000000..978654c03 --- /dev/null +++ b/components/x-teaser-timeline/stories/with-latest-items.js @@ -0,0 +1,13 @@ +exports.title = 'With latest items'; + +exports.data = { + items: require('./content-items.json'), + timezoneOffset: -60, + localTodayDate: '2018-10-17', + latestItemsTime: '2018-10-17T12:10:33.000Z', + itemCustomSlot: item => `(action)` +}; + +// This reference is only required for hot module loading in development +// +exports.m = module; diff --git a/components/x-teaser-timeline/stories/without-latest-items.js b/components/x-teaser-timeline/stories/without-latest-items.js new file mode 100644 index 000000000..cfbbd5cd0 --- /dev/null +++ b/components/x-teaser-timeline/stories/without-latest-items.js @@ -0,0 +1,12 @@ +exports.title = 'Without latest items'; + +exports.data = { + items: require('./content-items.json'), + timezoneOffset: -60, + localTodayDate: '2018-10-17', + itemCustomSlot: item => `(action)` +}; + +// This reference is only required for hot module loading in development +// +exports.m = module; diff --git a/tools/x-storybook/package.json b/tools/x-storybook/package.json index 23decb35c..34e53ca27 100644 --- a/tools/x-storybook/package.json +++ b/tools/x-storybook/package.json @@ -22,6 +22,7 @@ "@financial-times/x-increment": "file:../../components/x-increment", "@financial-times/x-styling-demo": "file:../../components/x-styling-demo", "@financial-times/x-teaser": "file:../../components/x-teaser", + "@financial-times/x-teaser-timeline": "file:../../components/x-teaser-timeline", "@storybook/addon-knobs": "^4.0.4", "@storybook/addon-viewport": "^4.0.4", "@storybook/addons": "^4.0.4", diff --git a/tools/x-storybook/register-components.js b/tools/x-storybook/register-components.js index be0649af3..aa8413902 100644 --- a/tools/x-storybook/register-components.js +++ b/tools/x-storybook/register-components.js @@ -2,6 +2,7 @@ const components = [ require('@financial-times/x-teaser/stories'), require('@financial-times/x-increment/stories'), require('@financial-times/x-styling-demo/stories'), + require('@financial-times/x-teaser-timeline/stories'), ]; module.exports = components; From 7eb8f2cd6b9914685231639cb1764e039f151e65 Mon Sep 17 00:00:00 2001 From: Dan Searle Date: Tue, 20 Nov 2018 12:26:07 +0000 Subject: [PATCH 013/760] Fix Teaser bottom border problem with opinion content. --- .../__snapshots__/snapshots.test.js.snap | 12 +++---- .../TeaserTimeline.test.jsx.snap | 36 +++++++++---------- .../x-teaser-timeline/src/TeaserTimeline.jsx | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index aaae53a66..ffda7cd1a 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -6291,7 +6291,7 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items className="TeaserTimeline_item__1s6ow" >
{ {typeof slotContent === 'string' ?
Date: Fri, 23 Nov 2018 12:03:21 +0000 Subject: [PATCH 014/760] Import date-fns functions from their separate files. Avoids Webpack importing everything causing bloat. --- components/x-teaser-timeline/src/lib/date.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/src/lib/date.js b/components/x-teaser-timeline/src/lib/date.js index 33c9a4309..a36271282 100644 --- a/components/x-teaser-timeline/src/lib/date.js +++ b/components/x-teaser-timeline/src/lib/date.js @@ -1,4 +1,8 @@ -import { format, isAfter, differenceInCalendarDays, subMinutes } from 'date-fns'; +// TODO: import from main date-fns entry point once ft-app is no longer using Webpack 1. +import differenceInCalendarDays from 'date-fns/difference_in_calendar_days'; +import format from 'date-fns/format'; +import isAfter from 'date-fns/is_after'; +import subMinutes from 'date-fns/sub_minutes'; /** * Takes a UTC ISO date/time and turns it into a ISO date for a particular timezone From 22e5c35fd15cc697b40f3789271a7dcfcb08a15e Mon Sep 17 00:00:00 2001 From: dan-searle Date: Tue, 27 Nov 2018 14:34:38 +0000 Subject: [PATCH 015/760] Render optional x-article-save-buttons instead of a custom slot. --- .../__snapshots__/snapshots.test.js.snap | 1404 ++++-- .../__tests__/TeaserTimeline.test.jsx | 39 +- .../TeaserTimeline.test.jsx.snap | 4392 +++++++++-------- components/x-teaser-timeline/package.json | 1 + components/x-teaser-timeline/readme.md | 4 +- .../x-teaser-timeline/src/TeaserTimeline.jsx | 40 +- .../x-teaser-timeline/src/TeaserTimeline.scss | 4 + .../stories/content-items.json | 3 +- .../stories/with-latest-items.js | 3 +- .../stories/without-latest-items.js | 3 +- 10 files changed, 3447 insertions(+), 2446 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index ffda7cd1a..745f8b255 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -6033,12 +6033,26 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items
(action)", - } - } - /> + > +
+ +
+
  • (action)", - } - } - /> + > +
    + +
    +
  • @@ -6202,12 +6230,26 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items
    (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • @@ -6532,12 +6616,26 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items
    (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • @@ -7295,12 +7519,26 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items
    (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • @@ -8081,12 +8459,26 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite
    (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • @@ -8567,12 +9029,26 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite
    (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • @@ -9330,12 +9932,26 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite
    (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • (action)", - } - } - /> + > +
    + +
    +
  • diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx index e0fe1c9c1..e79e644ef 100644 --- a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -53,48 +53,23 @@ describe('x-teaser-timeline', () => { }); }); - describe('itemCustomSlot', () => { - let mockItemActionsCreator; - - describe('given itemCustomSlot is a function', () => { + describe('showSaveButtons', () => { + describe('given showSaveButtons is not set or is true', () => { beforeEach(() => { - mockItemActionsCreator = jest.fn(item => `action for ${item.id}`); - tree = renderer.create().toJSON(); - }); - - it('should call the itemCustomSlot for each item, with each item', () => { - mockItemActionsCreator.mock.calls.forEach((c, i) => { - expect(c[0]).toEqual(expect.objectContaining(contentItems[i])); - }); - }); - - it('renders each item with the created item-specific action', () => { - expect(tree).toMatchSnapshot(); - }); - }); - describe('given itemCustomSlot is a JSX child', () => { - beforeEach(() => { - mockItemActionsCreator = I am an action; - tree = renderer.create().toJSON(); + tree = renderer.create().toJSON(); }); - it('renders each item with the action', () => { + it('renders save buttons by default', () => { expect(tree).toMatchSnapshot(); }); }); - describe('given itemCustomSlot is not set', () => { + describe('given showSaveButtons is set to false', () => { beforeEach(() => { - tree = renderer.create().toJSON(); + tree = renderer.create().toJSON(); }); - it('renders each item without an action', () => { + it('does not render the save buttons', () => { expect(tree).toMatchSnapshot(); }); }); diff --git a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap index 13d38fffa..9cc3eef55 100644 --- a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap +++ b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap @@ -82,6 +82,28 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
    @@ -520,6 +652,28 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
    @@ -1203,6 +1555,28 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • @@ -1901,6 +2495,28 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • @@ -2339,6 +3065,28 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • -
  • - - -
    +
    + +
    + + + +
    +

    +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • +
    +
    + +
    +
  • @@ -3720,6 +4908,28 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, +
    +
    + +
    +
  • +
    +
    + +
    +
  • @@ -3873,1834 +5105,28 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, - -
  • -
    -
    -
    - -
    - -

    - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border -

    -
    -
    -
    - -
    -
    -
    -
  • -
  • -
    -
    -
    -
    - - Inside Business - - - Sarah Gordon - -
    -
    - -

    - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth -

    -
    -
    -
    - -
    -
    -
    -
  • -
  • -
    - - -

    - White House makes London a priority ‘as soon as it is ready’ after Brexit -

    -
    -
    -
    - -
    -
    -
    -
  • - - -
    -

    - Yesterday -

    - -
    -
    -

    - October 15, 2018 -

    - -
    - -`; - -exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is a JSX child renders each item with the action 1`] = ` -
    -
    -

    - Earlier Today -

    -
    - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • @@ -6008,9 +5491,28 @@ exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is a JSX child re - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • @@ -6721,9 +6394,28 @@ exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is a JSX child re - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • - - I am an action - +
    +
    + +
    +
  • `; -exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is a function renders each item with the created item-specific action 1`] = ` +exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or is true renders save buttons by default 1`] = `
    + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • @@ -7940,12 +7906,26 @@ exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is a function ren
    + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • @@ -8703,12 +8809,26 @@ exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is a function ren
    + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • + > +
    + +
    +
  • `; -exports[`x-teaser-timeline itemCustomSlot given itemCustomSlot is not set renders each item without an action 1`] = ` +exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false does not render the save buttons 1`] = `
    { - const { itemCustomSlot = () => {} } = props; + const { + csrfToken = null, + showSaveButtons = true + } = props; const itemGroups = getItemGroups(props); return itemGroups.length && ( @@ -14,23 +18,25 @@ const TeaserTimeline = props => {

    {group.title}

      - {group.items.map(item => { - const slotContent = typeof itemCustomSlot === 'function' ? itemCustomSlot(item): itemCustomSlot; - - return ( -
    • - ( +
    • + + {showSaveButtons && +
      + - {typeof slotContent === 'string' ?
      : slotContent} -
    • - ); - })} +
    } + + ))} ))} diff --git a/components/x-teaser-timeline/src/TeaserTimeline.scss b/components/x-teaser-timeline/src/TeaserTimeline.scss index 8b5d300bd..2d456b0ec 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.scss +++ b/components/x-teaser-timeline/src/TeaserTimeline.scss @@ -2,6 +2,10 @@ @import 'o-grid/main'; @import 'o-typography/main'; +:global { + @import "~@financial-times/x-article-save-button/dist/ArticleSaveButton"; +} + .itemGroup { border-top: 4px solid #000; diff --git a/components/x-teaser-timeline/stories/content-items.json b/components/x-teaser-timeline/stories/content-items.json index 9b0adcbed..d198bb1a8 100644 --- a/components/x-teaser-timeline/stories/content-items.json +++ b/components/x-teaser-timeline/stories/content-items.json @@ -80,7 +80,8 @@ "height": 1152 }, "headshot": null, - "parentTheme": null + "parentTheme": null, + "saved": true }, { "id": "d4e80114-d1ee-11e8-a9f2-7574db66bcd5", diff --git a/components/x-teaser-timeline/stories/with-latest-items.js b/components/x-teaser-timeline/stories/with-latest-items.js index 978654c03..64f4e6080 100644 --- a/components/x-teaser-timeline/stories/with-latest-items.js +++ b/components/x-teaser-timeline/stories/with-latest-items.js @@ -4,8 +4,7 @@ exports.data = { items: require('./content-items.json'), timezoneOffset: -60, localTodayDate: '2018-10-17', - latestItemsTime: '2018-10-17T12:10:33.000Z', - itemCustomSlot: item => `(action)` + latestItemsTime: '2018-10-17T12:10:33.000Z' }; // This reference is only required for hot module loading in development diff --git a/components/x-teaser-timeline/stories/without-latest-items.js b/components/x-teaser-timeline/stories/without-latest-items.js index cfbbd5cd0..0b4fcc6da 100644 --- a/components/x-teaser-timeline/stories/without-latest-items.js +++ b/components/x-teaser-timeline/stories/without-latest-items.js @@ -3,8 +3,7 @@ exports.title = 'Without latest items'; exports.data = { items: require('./content-items.json'), timezoneOffset: -60, - localTodayDate: '2018-10-17', - itemCustomSlot: item => `(action)` + localTodayDate: '2018-10-17' }; // This reference is only required for hot module loading in development From 7ef618e223c86ae5c2905f9c6fdb638b45a746b7 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Wed, 28 Nov 2018 09:34:46 +0000 Subject: [PATCH 016/760] Update to x-article-save-button@0.0.7 --- components/x-teaser-timeline/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index f49480f14..04aae44f5 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -18,7 +18,7 @@ "author": "", "license": "ISC", "dependencies": { - "@financial-times/x-article-save-button": "0.0.6", + "@financial-times/x-article-save-button": "0.0.7", "@financial-times/x-engine": "file:../../packages/x-engine", "@financial-times/x-teaser": "file:../x-teaser", "classnames": "^2.2.6", From f8d19d0823e2b3b4b34d754cc9f811c5c76ceb91 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Tue, 4 Dec 2018 10:50:28 +0000 Subject: [PATCH 017/760] Use x-article-save-button@0.0.8 --- .../__snapshots__/snapshots.test.js.snap | 162 +++++++++ .../TeaserTimeline.test.jsx.snap | 324 ++++++++++++++++++ components/x-teaser-timeline/package.json | 2 +- 3 files changed, 487 insertions(+), 1 deletion(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 745f8b255..98da1408a 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -6049,6 +6049,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -6141,6 +6144,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Saved @@ -6246,6 +6252,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -6338,6 +6347,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -6435,6 +6447,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -6527,6 +6542,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -6632,6 +6650,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -6729,6 +6750,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -6821,6 +6845,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -6913,6 +6940,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7000,6 +7030,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7087,6 +7120,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7179,6 +7215,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7246,6 +7285,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7338,6 +7380,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7430,6 +7475,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7535,6 +7583,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7597,6 +7648,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7684,6 +7738,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7781,6 +7838,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7873,6 +7933,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -7965,6 +8028,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -8057,6 +8123,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -8144,6 +8213,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -8236,6 +8308,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -8303,6 +8378,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -8365,6 +8443,9 @@ exports[`@financial-times/x-teaser-timeline renders a default With latest items data-trackable="save-for-later" type="submit" > + Save @@ -8475,6 +8556,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -8567,6 +8651,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Saved @@ -8659,6 +8746,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -8751,6 +8841,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -8848,6 +8941,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -8940,6 +9036,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9045,6 +9144,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9142,6 +9244,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9234,6 +9339,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9326,6 +9434,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9413,6 +9524,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9500,6 +9614,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9592,6 +9709,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9659,6 +9779,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9751,6 +9874,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9843,6 +9969,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -9948,6 +10077,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10010,6 +10142,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10097,6 +10232,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10194,6 +10332,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10286,6 +10427,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10378,6 +10522,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10470,6 +10617,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10557,6 +10707,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10649,6 +10802,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10716,6 +10872,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save @@ -10778,6 +10937,9 @@ exports[`@financial-times/x-teaser-timeline renders a default Without latest ite data-trackable="save-for-later" type="submit" > + Save diff --git a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap index 9cc3eef55..294887a3e 100644 --- a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap +++ b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap @@ -100,6 +100,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -192,6 +195,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Saved @@ -284,6 +290,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -376,6 +385,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -473,6 +485,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -565,6 +580,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -670,6 +688,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -767,6 +788,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -859,6 +883,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -951,6 +978,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1038,6 +1068,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1125,6 +1158,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1217,6 +1253,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1284,6 +1323,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1376,6 +1418,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1468,6 +1513,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1573,6 +1621,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1635,6 +1686,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1722,6 +1776,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1819,6 +1876,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -1911,6 +1971,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -2003,6 +2066,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -2095,6 +2161,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -2182,6 +2251,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -2274,6 +2346,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -2341,6 +2416,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -2403,6 +2481,9 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes data-trackable="save-for-later" type="submit" > + Save @@ -2513,6 +2594,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -2605,6 +2689,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Saved @@ -2697,6 +2784,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -2789,6 +2879,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -2886,6 +2979,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -2978,6 +3074,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3083,6 +3182,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3180,6 +3282,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3272,6 +3377,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3364,6 +3472,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3451,6 +3562,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3538,6 +3652,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3630,6 +3747,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3697,6 +3817,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3789,6 +3912,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3881,6 +4007,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -3986,6 +4115,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4048,6 +4180,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4135,6 +4270,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4232,6 +4370,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4324,6 +4465,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4416,6 +4560,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4508,6 +4655,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4595,6 +4745,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4687,6 +4840,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4754,6 +4910,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4816,6 +4975,9 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as data-trackable="save-for-later" type="submit" > + Save @@ -4926,6 +5088,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5018,6 +5183,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Saved @@ -5123,6 +5291,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5215,6 +5386,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5312,6 +5486,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5404,6 +5581,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5509,6 +5689,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5606,6 +5789,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5698,6 +5884,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5790,6 +5979,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5877,6 +6069,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -5964,6 +6159,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6056,6 +6254,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6123,6 +6324,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6215,6 +6419,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6307,6 +6514,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6412,6 +6622,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6474,6 +6687,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6561,6 +6777,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6658,6 +6877,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6750,6 +6972,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6842,6 +7067,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -6934,6 +7162,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -7021,6 +7252,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -7113,6 +7347,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -7180,6 +7417,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -7242,6 +7482,9 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, data-trackable="save-for-later" type="submit" > + Save @@ -7352,6 +7595,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -7444,6 +7690,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Saved @@ -7536,6 +7785,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -7628,6 +7880,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -7725,6 +7980,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -7817,6 +8075,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -7922,6 +8183,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8019,6 +8283,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8111,6 +8378,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8203,6 +8473,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8290,6 +8563,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8377,6 +8653,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8469,6 +8748,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8536,6 +8818,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8628,6 +8913,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8720,6 +9008,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8825,6 +9116,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8887,6 +9181,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -8974,6 +9271,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -9071,6 +9371,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -9163,6 +9466,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -9255,6 +9561,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -9347,6 +9656,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -9434,6 +9746,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -9526,6 +9841,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -9593,6 +9911,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save @@ -9655,6 +9976,9 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i data-trackable="save-for-later" type="submit" > + Save diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index 04aae44f5..031e9c78c 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -18,7 +18,7 @@ "author": "", "license": "ISC", "dependencies": { - "@financial-times/x-article-save-button": "0.0.7", + "@financial-times/x-article-save-button": "0.0.8", "@financial-times/x-engine": "file:../../packages/x-engine", "@financial-times/x-teaser": "file:../x-teaser", "classnames": "^2.2.6", From dda238017881c40bc826dff8411da0b5eafa3dca Mon Sep 17 00:00:00 2001 From: dan-searle Date: Tue, 4 Dec 2018 16:57:57 +0000 Subject: [PATCH 018/760] Use x-article-save-button@0.0.9 (no focus outline) --- components/x-teaser-timeline/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index 031e9c78c..65d9d5c18 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -18,7 +18,7 @@ "author": "", "license": "ISC", "dependencies": { - "@financial-times/x-article-save-button": "0.0.8", + "@financial-times/x-article-save-button": "0.0.9", "@financial-times/x-engine": "file:../../packages/x-engine", "@financial-times/x-teaser": "file:../x-teaser", "classnames": "^2.2.6", From 1ba31406d604aadb9183eff8fe9df553b450d491 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Wed, 5 Dec 2018 14:20:29 +0000 Subject: [PATCH 019/760] Only run bower install on prepare --- components/x-teaser-timeline/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index 65d9d5c18..121822c7b 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -7,10 +7,9 @@ "browser": "dist/TeaserTimeline.es5.js", "style": "dist/TeaserTimeline.css", "scripts": { - "prepare": "npm run build", + "prepare": "bower install && npm run build", "build": "node rollup.js", - "start": "node rollup.js --watch", - "postinstall": "bower install" + "start": "node rollup.js --watch" }, "keywords": [ "x-dash" From 156cce414de6f342fc79e5f33daad8ce991b51e9 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Thu, 6 Dec 2018 15:26:30 +0000 Subject: [PATCH 020/760] Document teaser styles needed to be imported by the consumer of x-teaser-timeline. --- components/x-teaser-timeline/readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md index 03fa38789..4f3362635 100644 --- a/components/x-teaser-timeline/readme.md +++ b/components/x-teaser-timeline/readme.md @@ -15,6 +15,17 @@ The [`x-engine`][engine] module is used to inject your chosen runtime into the c [engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +## Other dependencies + +[o-teaser](https://registry.origami.ft.com/components/o-teaser) styles will need to be imported by the consumer of this component. + +If selectively importing o-teaser's styles via scss, then you will need the following: + +```scss +$o-teaser-is-silent: true; +@import 'o-teaser/main'; +@include oTeaser(('default', 'images', 'timestamp'), ('small')); +``` ## Usage From c04df78e9b44b08155159a0f32098e399f514423 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Thu, 6 Dec 2018 16:57:19 +0000 Subject: [PATCH 021/760] Use x-article-save-button@0.0.10 --- components/x-teaser-timeline/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index 121822c7b..dd8d5bf98 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -17,7 +17,7 @@ "author": "", "license": "ISC", "dependencies": { - "@financial-times/x-article-save-button": "0.0.9", + "@financial-times/x-article-save-button": "0.0.10", "@financial-times/x-engine": "file:../../packages/x-engine", "@financial-times/x-teaser": "file:../x-teaser", "classnames": "^2.2.6", From 36ac94dedbdb8dfd6ff0be19c5527d22f1f79589 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Mon, 10 Dec 2018 12:33:53 +0000 Subject: [PATCH 022/760] Update to x-article-save-button@0.0.11 --- components/x-teaser-timeline/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index dd8d5bf98..a774ff4e0 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -17,7 +17,7 @@ "author": "", "license": "ISC", "dependencies": { - "@financial-times/x-article-save-button": "0.0.10", + "@financial-times/x-article-save-button": "0.0.11", "@financial-times/x-engine": "file:../../packages/x-engine", "@financial-times/x-teaser": "file:../x-teaser", "classnames": "^2.2.6", From fb4a623ec9c22b543261ead795233e32c46f7031 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Wed, 16 Jan 2019 10:33:51 +0000 Subject: [PATCH 023/760] Support custom slot --- .../__tests__/TeaserTimeline.test.jsx | 35 ++++++++++- components/x-teaser-timeline/readme.md | 2 + .../x-teaser-timeline/src/TeaserTimeline.jsx | 61 ++++++++++++------- .../x-teaser-timeline/src/lib/transform.js | 17 ++++++ 4 files changed, 91 insertions(+), 24 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx index e79e644ef..437c27dbb 100644 --- a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -1,6 +1,6 @@ const renderer = require('react-test-renderer'); const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { shallow } = require('@financial-times/x-test-utils/enzyme'); const contentItems = require('../stories/content-items.json'); const { TeaserTimeline } = require('../'); @@ -80,11 +80,42 @@ describe('x-teaser-timeline', () => { beforeEach(() => { delete props.items; - component = mount(); + component = shallow(); }); it('should render nothing', () => { expect(component.html()).toEqual(null); }) }); + + describe('custom slot', () => { + let component; + + describe('custom slot without latestArticlesTime set', () => { + beforeEach(() => { + props.customSlotContent = '
    Custom slot content
    '; + props.customSlotPosition = 3; + component = shallow(); + }); + + it('has custom content in correct position', () => { + expect(component.render().find('.custom-slot')).toHaveLength(1); + expect(component.render().find('li').eq(3).find('.custom-slot')).toHaveLength(1); + }); + }); + + describe('custom slot with latestArticlesTime set', () => { + beforeEach(() => { + props.customSlotContent = '
    Custom slot content
    '; + props.customSlotPosition = 2; + props.latestItemsTime = '2018-10-16T12:10:33.000Z'; + component = shallow(); + }); + + it('has custom content in correct position', () => { + expect(component.render().find('.custom-slot')).toHaveLength(1); + expect(component.render().find('li').eq(2).find('.custom-slot')).toHaveLength(1); + }); + }); + }); }); diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md index 4f3362635..f026b1ff6 100644 --- a/components/x-teaser-timeline/readme.md +++ b/components/x-teaser-timeline/readme.md @@ -54,6 +54,8 @@ Feature | Type | Notes `localTodayDate` | String | (Defaults using runtime clock) ISO format YYYY-MM-DD representating today's date in the user's timezone. `latestItemsTime` | String | ISO time (HH:mm:ss). If provided, will be used in combination with `localTodayDate` to render today's items into separate "Latest" and "Earlier" groups. `showSaveButtons` | Boolean | (Default to true). Option to hide x-article-save-buttons if they are not needed. Those buttons will get their saved/unsaved state from a `saved` property of the content item. +`customSlotContent` | String | Content to insert at `customSlotPosition`. +`customSlotPosition` | Number | (Default is 2). Where to insert `customSlotContent`. The custom content will be inserted after the item at this position number. If this position is greater than the number items to render, then it will be inserted last. Example: diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index 64d5f505d..841cf105c 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -1,42 +1,59 @@ import { h } from '@financial-times/x-engine'; import { ArticleSaveButton } from '@financial-times/x-article-save-button'; import { Teaser, presets } from '@financial-times/x-teaser'; -import { getItemGroups } from './lib/transform'; +import { getGroupAndIndex, getItemGroups } from './lib/transform'; import styles from './TeaserTimeline.scss'; import classNames from 'classnames'; const TeaserTimeline = props => { const { csrfToken = null, + customSlotContent, + customSlotPosition = 2, + items, showSaveButtons = true } = props; const itemGroups = getItemGroups(props); - return itemGroups.length && ( + if (customSlotContent) { + const insertPosition = Math.min(customSlotPosition, items.length); + const insert = getGroupAndIndex(itemGroups, insertPosition); + + itemGroups[insert.group].items.splice(insert.index, 0, customSlotContent); + } + + return itemGroups.length > 0 && (
    {itemGroups.map(group => (

    {group.title}

      - {group.items.map(item => ( -
    • - - {showSaveButtons && -
      - -
      } -
    • - ))} + {group.items.map(item => { + if (item.id) { + return ( +
    • + + {showSaveButtons && +
      + +
      } +
    • + ); + } + if (typeof item === 'string') { + return (
    • ); + } + })}
    ))} @@ -44,4 +61,4 @@ const TeaserTimeline = props => { ); }; -export { TeaserTimeline }; +export { TeaserTimeline, getGroupAndIndex }; diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index bf18eda56..f69238455 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -81,3 +81,20 @@ export const getItemGroups = props => { return addItemGroupTitles(itemGroups, localTodayDate); }; + +export const getGroupAndIndex = (groups, position) => { + if (position > 0) { + const group = groups.findIndex(g => g.items.some(item => item.articleIndex === position - 1)); + const index = groups[group].items.findIndex(item => item.articleIndex === position - 1); + + return { + group: group, + index: index + 1 + }; + } + + return { + group: 0, + item: 0 + }; +}; From 78fe204f24d29e3f66da0cf6e5aabeb99c137a15 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Wed, 16 Jan 2019 10:34:18 +0000 Subject: [PATCH 024/760] Replace separate knobless stories with a single one that has knobs. --- .../__snapshots__/snapshots.test.js.snap | 2501 +---------------- components/x-teaser-timeline/stories/index.js | 5 +- components/x-teaser-timeline/stories/knobs.js | 24 + .../{with-latest-items.js => timeline.js} | 8 +- .../stories/without-latest-items.js | 11 - 5 files changed, 40 insertions(+), 2509 deletions(-) create mode 100644 components/x-teaser-timeline/stories/knobs.js rename components/x-teaser-timeline/stories/{with-latest-items.js => timeline.js} (59%) delete mode 100644 components/x-teaser-timeline/stories/without-latest-items.js diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 98da1408a..ca2f09d40 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -5949,7 +5949,7 @@ exports[`@financial-times/x-teaser renders a TopStoryLandscape Video x-teaser 1`
    `; -exports[`@financial-times/x-teaser-timeline renders a default With latest items x-teaser-timeline 1`] = ` +exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-timeline 1`] = `
  • -
    -
    - - -

    - White House makes London a priority ‘as soon as it is ready’ after Brexit -

    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
  • - -
    -
    -

    - Yesterday -

    - -
    -
    -

    - October 15, 2018 -

    - -
    -
    -`; - -exports[`@financial-times/x-teaser-timeline renders a default Without latest items x-teaser-timeline 1`] = ` -
    -
    -

    - Earlier Today -

    -
      -
    • -
      -
      - - -

      - As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely -

      -
      -
      -
      - -
      -
      -
      -
      -
      - -
      -
      -
    • -
    • -
      -
      -
      - -
      - -

      - UK and EU consider extending transition deal to defuse dispute over Irish border backstop -

      -
      -
      -
      - -
      -
      -
      -
      -
      - -
      -
      -
    • -
    • -
      -
      -
      - -
      - -

      - Quarterly survey shows more companies now believe Brexit will damage their business than help them -

      -
      -
      -
      - -
      -
      -
      -
      -
      - -
      -
      -
    • -
    • -
      -
      -
      - -
      - -

      - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border -

      -
      -
      -
      - -
      -
      -
      -
      -
      - -
      -
      -
    • -
    • -
      -
      -
      -
      - - Inside Business - - - Sarah Gordon - -
      -
      - -

      - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth -

      -
      -
      -
      - -
      -
      -
      -
      -
      - -
      -
      -
    • + dangerouslySetInnerHTML={ + Object { + "__html": "Custom slot content", + } + } + />
    • diff --git a/components/x-teaser-timeline/stories/index.js b/components/x-teaser-timeline/stories/index.js index c3c07da70..c2a2753b5 100644 --- a/components/x-teaser-timeline/stories/index.js +++ b/components/x-teaser-timeline/stories/index.js @@ -12,6 +12,7 @@ exports.dependencies = { }; exports.stories = [ - require('./without-latest-items'), - require('./with-latest-items'), + require('./timeline'), ]; + +exports.knobs = require('./knobs'); diff --git a/components/x-teaser-timeline/stories/knobs.js b/components/x-teaser-timeline/stories/knobs.js new file mode 100644 index 000000000..c4d730842 --- /dev/null +++ b/components/x-teaser-timeline/stories/knobs.js @@ -0,0 +1,24 @@ +module.exports = (data, { number, select }) => { + return { + latestItemsTime() { + return select( + 'Latest Items Time', + { + None: '', + '2018-10-17T12:10:33.000Z': '2018-10-17T12:10:33.000Z' + }); + }, + customSlotPosition() { + return number('Custom Slot Position', data.customSlotPosition); + }, + customSlotContent() { + return select( + 'Custom Slot Content', + { + None: '', + Something: '---Custom slot content---' + }); + } + }; +}; + diff --git a/components/x-teaser-timeline/stories/with-latest-items.js b/components/x-teaser-timeline/stories/timeline.js similarity index 59% rename from components/x-teaser-timeline/stories/with-latest-items.js rename to components/x-teaser-timeline/stories/timeline.js index 64f4e6080..da93a2ad1 100644 --- a/components/x-teaser-timeline/stories/with-latest-items.js +++ b/components/x-teaser-timeline/stories/timeline.js @@ -1,12 +1,16 @@ -exports.title = 'With latest items'; +exports.title = 'Timeline'; exports.data = { items: require('./content-items.json'), timezoneOffset: -60, localTodayDate: '2018-10-17', - latestItemsTime: '2018-10-17T12:10:33.000Z' + latestItemsTime: '2018-10-17T12:10:33.000Z', + customSlotContent: 'Custom slot content', + customSlotPosition: 3 }; +exports.knobs = Object.keys(exports.data); + // This reference is only required for hot module loading in development // exports.m = module; diff --git a/components/x-teaser-timeline/stories/without-latest-items.js b/components/x-teaser-timeline/stories/without-latest-items.js deleted file mode 100644 index 0b4fcc6da..000000000 --- a/components/x-teaser-timeline/stories/without-latest-items.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.title = 'Without latest items'; - -exports.data = { - items: require('./content-items.json'), - timezoneOffset: -60, - localTodayDate: '2018-10-17' -}; - -// This reference is only required for hot module loading in development -// -exports.m = module; From 4f56c31e2f41188667b00bb97a4fcf9bc23f5663 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Wed, 16 Jan 2019 13:21:17 +0000 Subject: [PATCH 025/760] Fix eslint-plugin-react version so build passes. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1f5b1608..670e32ed3 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "eslint-config-prettier": "^2.9.0", "eslint-plugin-jest": "^21.17.0", "eslint-plugin-jsx-a11y": "^6.1.1", - "eslint-plugin-react": "^7.10.0", + "eslint-plugin-react": "7.10.0", "espree": "^4.1.0", "fetch-mock": "^6.5.2", "react": "^16.3.1", From 66acbd56b9a52a48109d5d4cf8cc6b1ebf74b5f3 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Wed, 16 Jan 2019 13:32:31 +0000 Subject: [PATCH 026/760] Update snapshot for x-teaser-timeline --- __tests__/__snapshots__/snapshots.test.js.snap | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index ca2f09d40..a2cc42241 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -6057,6 +6057,13 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    +
  • @@ -6455,13 +6462,6 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
  • -
  • From fd9bc16bb16e04b01cb361aa4085e5a6f408c061 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Fri, 18 Jan 2019 14:23:41 +0000 Subject: [PATCH 027/760] No need to export getGroupAndIndex --- components/x-teaser-timeline/src/TeaserTimeline.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index 841cf105c..32aabb653 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -61,4 +61,4 @@ const TeaserTimeline = props => { ); }; -export { TeaserTimeline, getGroupAndIndex }; +export { TeaserTimeline }; From e99893b6ccc9ef4cda6290c7a975dd39fee2b8bc Mon Sep 17 00:00:00 2001 From: dan-searle Date: Mon, 21 Jan 2019 14:48:41 +0000 Subject: [PATCH 028/760] Support JSX/node as customSlotContent. --- .../__tests__/TeaserTimeline.test.jsx | 41 +++++++++++++------ .../x-teaser-timeline/src/TeaserTimeline.jsx | 5 ++- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx index 437c27dbb..661eaf727 100644 --- a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -91,30 +91,45 @@ describe('x-teaser-timeline', () => { describe('custom slot', () => { let component; - describe('custom slot without latestArticlesTime set', () => { - beforeEach(() => { - props.customSlotContent = '
    Custom slot content
    '; - props.customSlotPosition = 3; - component = shallow(); + describe('custom slot content is a string', () => { + describe('without latestArticlesTime set', () => { + beforeEach(() => { + props.customSlotContent = '
    Custom slot content
    '; + props.customSlotPosition = 3; + component = shallow(); + }); + + it('has custom content in correct position', () => { + expect(component.render().find('.custom-slot')).toHaveLength(1); + expect(component.render().find('li').eq(3).find('.custom-slot')).toHaveLength(1); + }); }); - it('has custom content in correct position', () => { - expect(component.render().find('.custom-slot')).toHaveLength(1); - expect(component.render().find('li').eq(3).find('.custom-slot')).toHaveLength(1); + describe('with latestArticlesTime set', () => { + beforeEach(() => { + props.customSlotContent = '
    Custom slot content
    '; + props.customSlotPosition = 2; + props.latestItemsTime = '2018-10-16T12:10:33.000Z'; + component = shallow(); + }); + + it('has custom content in correct position', () => { + expect(component.render().find('.custom-slot')).toHaveLength(1); + expect(component.render().find('li').eq(2).find('.custom-slot')).toHaveLength(1); + }); }); }); - describe('custom slot with latestArticlesTime set', () => { + describe('custom slot content is a node', () => { beforeEach(() => { - props.customSlotContent = '
    Custom slot content
    '; - props.customSlotPosition = 2; - props.latestItemsTime = '2018-10-16T12:10:33.000Z'; + props.customSlotContent =
    Custom slot content
    ; + props.customSlotPosition = 3; component = shallow(); }); it('has custom content in correct position', () => { expect(component.render().find('.custom-slot')).toHaveLength(1); - expect(component.render().find('li').eq(2).find('.custom-slot')).toHaveLength(1); + expect(component.render().find('li').eq(3).find('.custom-slot')).toHaveLength(1); }); }); }); diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index 32aabb653..61cb58612 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -49,10 +49,11 @@ const TeaserTimeline = props => { }
  • ); - } - if (typeof item === 'string') { + } else if (typeof item === 'string') { return (
  • ); } + + return (
  • {item}
  • ); })} From 94c4ffce64cc913ce07474d6a807b4d40ecce615 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Tue, 22 Jan 2019 12:02:42 +0000 Subject: [PATCH 029/760] Handle scenario where latestItemTime results in no "earlier today" items to show. --- .../__tests__/TeaserTimeline.test.jsx | 50 +++++++++++++++---- .../x-teaser-timeline/src/lib/transform.js | 19 ++++--- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx index 661eaf727..b13db1fe7 100644 --- a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -30,6 +30,24 @@ describe('x-teaser-timeline', () => { }); }); + describe('given latestItemsTime is set and results in all today`s articles being "latest"', () => { + let component; + + beforeEach(() => { + component = shallow( + ); + }); + + it('does not render the empty "earlier today" group', () => { + expect(component.render().find('section')).toHaveLength(3); + expect(component.render().find('section h2').text().toLowerCase().includes('earlier today')).toBe(false); + }); + }); + describe('given latestItemsTime is not set', () => { beforeEach(() => { tree = renderer.create().toJSON(); @@ -94,9 +112,13 @@ describe('x-teaser-timeline', () => { describe('custom slot content is a string', () => { describe('without latestArticlesTime set', () => { beforeEach(() => { - props.customSlotContent = '
    Custom slot content
    '; - props.customSlotPosition = 3; - component = shallow(); + component = shallow( + + ); }); it('has custom content in correct position', () => { @@ -107,10 +129,14 @@ describe('x-teaser-timeline', () => { describe('with latestArticlesTime set', () => { beforeEach(() => { - props.customSlotContent = '
    Custom slot content
    '; - props.customSlotPosition = 2; - props.latestItemsTime = '2018-10-16T12:10:33.000Z'; - component = shallow(); + component = shallow( + + ); }); it('has custom content in correct position', () => { @@ -122,9 +148,13 @@ describe('x-teaser-timeline', () => { describe('custom slot content is a node', () => { beforeEach(() => { - props.customSlotContent =
    Custom slot content
    ; - props.customSlotPosition = 3; - component = shallow(); + component = shallow( + Custom slot content} + customSlotPosition={3} + /> + ); }); it('has custom content in correct position', () => { diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index f69238455..dd13d4433 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -36,19 +36,26 @@ export const splitTodaysItems = (itemGroups, localTodayDate, latestItemsTime) => } const { latestItems, earlierItems } = splitLatestEarlier(itemGroups[0].items, latestItemsTime); - - itemGroups[0] = { - date: 'today-earlier', - items: earlierItems - }; + const splitGroups = []; if (latestItems.length) { - itemGroups.unshift({ + splitGroups.push({ date: 'today-latest', items: latestItems }); } + if (earlierItems.length) { + splitGroups.push({ + date: 'today-earlier', + items: earlierItems + }); + } + + if (splitGroups.length) { + itemGroups.splice(0, 1, ...splitGroups); + } + return itemGroups; }; From f320d0ad3c27e41a885fbad0b93054276c90df19 Mon Sep 17 00:00:00 2001 From: Ben Barnett Date: Fri, 25 Jan 2019 16:37:49 +0000 Subject: [PATCH 030/760] Add some IE specific CSS grid syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The layout was broken in IE11. Strangely though, autoprefixer should‘ve taken care of most of it as it does in the [next-article app](https://github.com/Financial-Times/next-article/blob/master/client/components/grid/_helpers.scss) This fixes the more immediate issue however. --- components/x-teaser-timeline/src/TeaserTimeline.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/src/TeaserTimeline.scss b/components/x-teaser-timeline/src/TeaserTimeline.scss index 2d456b0ec..14f66575a 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.scss +++ b/components/x-teaser-timeline/src/TeaserTimeline.scss @@ -12,7 +12,9 @@ @include oGridRespondTo($from: M) { display: grid; grid-gap: 0 20px; - grid-template: "heading articles" min-content / 1fr 3fr; + grid-template-columns: 1fr 3fr; + grid-template-areas: "heading articles"; + } } @@ -22,6 +24,8 @@ @include oGridRespondTo($from: M) { grid-area: heading; + -ms-grid-row: 1; + -ms-grid-column: 1; } } @@ -32,6 +36,8 @@ @include oGridRespondTo($from: M) { grid-area: articles; + -ms-grid-row: 1; + -ms-grid-column: 3; } } From f03753d6bace453978f9b2603bcfa19f7455e398 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Thu, 18 Apr 2019 14:34:44 +0100 Subject: [PATCH 031/760] Import date-fns functions via main entry point. --- components/x-teaser-timeline/src/lib/date.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/x-teaser-timeline/src/lib/date.js b/components/x-teaser-timeline/src/lib/date.js index a36271282..53873a08a 100644 --- a/components/x-teaser-timeline/src/lib/date.js +++ b/components/x-teaser-timeline/src/lib/date.js @@ -1,8 +1,4 @@ -// TODO: import from main date-fns entry point once ft-app is no longer using Webpack 1. -import differenceInCalendarDays from 'date-fns/difference_in_calendar_days'; -import format from 'date-fns/format'; -import isAfter from 'date-fns/is_after'; -import subMinutes from 'date-fns/sub_minutes'; +import { differenceInCalendarDays, format, isAfter, subMinutes } from 'date-fns'; /** * Takes a UTC ISO date/time and turns it into a ISO date for a particular timezone From 1f62daf566c1461f379542099109f4dfa3dc11c4 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Thu, 18 Apr 2019 14:57:17 +0100 Subject: [PATCH 032/760] Use x-teaser's modifiers prop instead of theme. --- .../__snapshots__/snapshots.test.js.snap | 48 ++-- .../TeaserTimeline.test.jsx.snap | 240 +++++++++--------- .../x-teaser-timeline/src/TeaserTimeline.jsx | 2 +- 3 files changed, 145 insertions(+), 145 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index a2cc42241..fb30568fc 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -5966,7 +5966,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser- className="TeaserTimeline_item__1s6ow" >
    { {showSaveButtons &&
    From ab063a561e5652191a687fe0008484a0666ba19c Mon Sep 17 00:00:00 2001 From: dan-searle Date: Thu, 18 Apr 2019 15:16:16 +0100 Subject: [PATCH 033/760] Don't fix version of eslint-plugin-react --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 670e32ed3..d1f5b1608 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "eslint-config-prettier": "^2.9.0", "eslint-plugin-jest": "^21.17.0", "eslint-plugin-jsx-a11y": "^6.1.1", - "eslint-plugin-react": "7.10.0", + "eslint-plugin-react": "^7.10.0", "espree": "^4.1.0", "fetch-mock": "^6.5.2", "react": "^16.3.1", From 7a7bdc297333426a8cf25a869c5d84c2901df683 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Thu, 18 Apr 2019 15:47:01 +0100 Subject: [PATCH 034/760] Clone array before inserting the custom content. --- components/x-teaser-timeline/src/TeaserTimeline.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index ac75de0ae..562b22900 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -18,8 +18,11 @@ const TeaserTimeline = props => { if (customSlotContent) { const insertPosition = Math.min(customSlotPosition, items.length); const insert = getGroupAndIndex(itemGroups, insertPosition); + const copyOfItems = [...itemGroups[insert.group].items]; - itemGroups[insert.group].items.splice(insert.index, 0, customSlotContent); + copyOfItems.splice(insert.index, 0, customSlotContent); + + itemGroups[insert.group].items = copyOfItems; } return itemGroups.length > 0 && ( From b3a2db31a40b3238da5884917430a649a6807a5c Mon Sep 17 00:00:00 2001 From: dan-searle Date: Thu, 18 Apr 2019 15:51:50 +0100 Subject: [PATCH 035/760] Document csrfToken prop in readme. --- components/x-teaser-timeline/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md index f026b1ff6..c0730f64e 100644 --- a/components/x-teaser-timeline/readme.md +++ b/components/x-teaser-timeline/readme.md @@ -56,6 +56,7 @@ Feature | Type | Notes `showSaveButtons` | Boolean | (Default to true). Option to hide x-article-save-buttons if they are not needed. Those buttons will get their saved/unsaved state from a `saved` property of the content item. `customSlotContent` | String | Content to insert at `customSlotPosition`. `customSlotPosition` | Number | (Default is 2). Where to insert `customSlotContent`. The custom content will be inserted after the item at this position number. If this position is greater than the number items to render, then it will be inserted last. +`csrfToken` | String | A CSRF token that will be used by the save buttons (if shown). Example: From 339bb52f736e8f5f85f0f67547d5c8af9a8f3777 Mon Sep 17 00:00:00 2001 From: dan-searle Date: Thu, 18 Apr 2019 16:14:23 +0100 Subject: [PATCH 036/760] Update x-teaser-timeline snapshots for latest x-teaser. --- .../__snapshots__/snapshots.test.js.snap | 194 +++- .../TeaserTimeline.test.jsx.snap | 900 ++++++++++++++++-- 2 files changed, 967 insertions(+), 127 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index fb30568fc..52fd07161 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -6002,7 +6002,14 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-

    - As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely + + As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely +

    -
  • @@ -6104,7 +6104,14 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-

    - UK and EU consider extending transition deal to defuse dispute over Irish border backstop + + UK and EU consider extending transition deal to defuse dispute over Irish border backstop +

  • - Quarterly survey shows more companies now believe Brexit will damage their business than help them + + Quarterly survey shows more companies now believe Brexit will damage their business than help them +

    +
  • @@ -6307,7 +6328,14 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-

    - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border + + In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border +

  • - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth + + Governments should be more concerned about SMEs: if supply chains falter, so will economic growth +

    - White House makes London a priority ‘as soon as it is ready’ after Brexit + + White House makes London a priority ‘as soon as it is ready’ after Brexit +

    - Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ + + Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ +

    - Speaker John Bercow should take responsibility — and go now + + Speaker John Bercow should take responsibility — and go now +

    - May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ + + May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ +

    - Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself + + Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself +

    - Universal credit system not expected to be fully operational until December 2023 + + Universal credit system not expected to be fully operational until December 2023 +

    - Analysts remain cautious despite optimistic data + + Analysts remain cautious despite optimistic data +

    @@ -7340,7 +7424,14 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-

    - Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits + + Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits +

    - US digital start-up to scale up EU operations with new Dublin branch + + US digital start-up to scale up EU operations with new Dublin branch +

    - Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely + + Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely +

    - Sterling has experienced several periods of volatility in the past 48 years + + Sterling has experienced several periods of volatility in the past 48 years +

    - Taoiseach says he always believed a deal this month was unlikely + + Taoiseach says he always believed a deal this month was unlikely +

    - While ministers haggle, company bosses press the button on expensive no-deal planning + + While ministers haggle, company bosses press the button on expensive no-deal planning +

    - MoD restarts competition despite industry’s concerns over budget and timing + + MoD restarts competition despite industry’s concerns over budget and timing +

    - Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources + + Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources +

    - As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely + + As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely +

    - UK and EU consider extending transition deal to defuse dispute over Irish border backstop + + UK and EU consider extending transition deal to defuse dispute over Irish border backstop +

    - Quarterly survey shows more companies now believe Brexit will damage their business than help them + + Quarterly survey shows more companies now believe Brexit will damage their business than help them +

    - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border + + In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border +

    - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth + + Governments should be more concerned about SMEs: if supply chains falter, so will economic growth +

    - White House makes London a priority ‘as soon as it is ready’ after Brexit + + White House makes London a priority ‘as soon as it is ready’ after Brexit +

    - Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ + + Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ +

    - Speaker John Bercow should take responsibility — and go now + + Speaker John Bercow should take responsibility — and go now +

    - May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ + + May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ +

    - Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself + + Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself +

    - Universal credit system not expected to be fully operational until December 2023 + + Universal credit system not expected to be fully operational until December 2023 +

    - Analysts remain cautious despite optimistic data + + Analysts remain cautious despite optimistic data +

    @@ -1371,7 +1455,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes

    - Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits + + Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits +

    - US digital start-up to scale up EU operations with new Dublin branch + + US digital start-up to scale up EU operations with new Dublin branch +

    - Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely + + Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely +

    - Sterling has experienced several periods of volatility in the past 48 years + + Sterling has experienced several periods of volatility in the past 48 years +

    - Taoiseach says he always believed a deal this month was unlikely + + Taoiseach says he always believed a deal this month was unlikely +

    - While ministers haggle, company bosses press the button on expensive no-deal planning + + While ministers haggle, company bosses press the button on expensive no-deal planning +

    - MoD restarts competition despite industry’s concerns over budget and timing + + MoD restarts competition despite industry’s concerns over budget and timing +

    - Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources + + Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources +

    - As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely + + As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely +

    - UK and EU consider extending transition deal to defuse dispute over Irish border backstop + + UK and EU consider extending transition deal to defuse dispute over Irish border backstop +

    - Quarterly survey shows more companies now believe Brexit will damage their business than help them + + Quarterly survey shows more companies now believe Brexit will damage their business than help them +

    - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border + + In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border +

    - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth + + Governments should be more concerned about SMEs: if supply chains falter, so will economic growth +

    - White House makes London a priority ‘as soon as it is ready’ after Brexit + + White House makes London a priority ‘as soon as it is ready’ after Brexit +

    - Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ + + Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ +

    - Speaker John Bercow should take responsibility — and go now + + Speaker John Bercow should take responsibility — and go now +

    - May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ + + May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ +

    - Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself + + Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself +

    - Universal credit system not expected to be fully operational until December 2023 + + Universal credit system not expected to be fully operational until December 2023 +

    - Analysts remain cautious despite optimistic data + + Analysts remain cautious despite optimistic data +

    @@ -3865,7 +4089,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as

    - Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits + + Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits +

    - US digital start-up to scale up EU operations with new Dublin branch + + US digital start-up to scale up EU operations with new Dublin branch +

    - Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely + + Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely +

    - Sterling has experienced several periods of volatility in the past 48 years + + Sterling has experienced several periods of volatility in the past 48 years +

    - Taoiseach says he always believed a deal this month was unlikely + + Taoiseach says he always believed a deal this month was unlikely +

    - While ministers haggle, company bosses press the button on expensive no-deal planning + + While ministers haggle, company bosses press the button on expensive no-deal planning +

    - MoD restarts competition despite industry’s concerns over budget and timing + + MoD restarts competition despite industry’s concerns over budget and timing +

    - Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources + + Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources +

    - As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely + + As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely +

    - UK and EU consider extending transition deal to defuse dispute over Irish border backstop + + UK and EU consider extending transition deal to defuse dispute over Irish border backstop +

    - Quarterly survey shows more companies now believe Brexit will damage their business than help them + + Quarterly survey shows more companies now believe Brexit will damage their business than help them +

    - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border + + In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border +

    - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth + + Governments should be more concerned about SMEs: if supply chains falter, so will economic growth +

    - White House makes London a priority ‘as soon as it is ready’ after Brexit + + White House makes London a priority ‘as soon as it is ready’ after Brexit +

    - Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ + + Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ +

    - Speaker John Bercow should take responsibility — and go now + + Speaker John Bercow should take responsibility — and go now +

    - May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ + + May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ +

    - Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself + + Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself +

    - Universal credit system not expected to be fully operational until December 2023 + + Universal credit system not expected to be fully operational until December 2023 +

    - Analysts remain cautious despite optimistic data + + Analysts remain cautious despite optimistic data +

    @@ -6372,7 +6736,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,

    - Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits + + Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits +

    - US digital start-up to scale up EU operations with new Dublin branch + + US digital start-up to scale up EU operations with new Dublin branch +

    - Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely + + Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely +

    - Sterling has experienced several periods of volatility in the past 48 years + + Sterling has experienced several periods of volatility in the past 48 years +

    - Taoiseach says he always believed a deal this month was unlikely + + Taoiseach says he always believed a deal this month was unlikely +

    - While ministers haggle, company bosses press the button on expensive no-deal planning + + While ministers haggle, company bosses press the button on expensive no-deal planning +

    - MoD restarts competition despite industry’s concerns over budget and timing + + MoD restarts competition despite industry’s concerns over budget and timing +

    - Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources + + Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources +

    - As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely + + As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely +

    - UK and EU consider extending transition deal to defuse dispute over Irish border backstop + + UK and EU consider extending transition deal to defuse dispute over Irish border backstop +

    - Quarterly survey shows more companies now believe Brexit will damage their business than help them + + Quarterly survey shows more companies now believe Brexit will damage their business than help them +

    - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border + + In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border +

    - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth + + Governments should be more concerned about SMEs: if supply chains falter, so will economic growth +

    - White House makes London a priority ‘as soon as it is ready’ after Brexit + + White House makes London a priority ‘as soon as it is ready’ after Brexit +

    - Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ + + Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ +

    - Speaker John Bercow should take responsibility — and go now + + Speaker John Bercow should take responsibility — and go now +

    - May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ + + May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ +

    - Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself + + Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself +

    - Universal credit system not expected to be fully operational until December 2023 + + Universal credit system not expected to be fully operational until December 2023 +

    - Analysts remain cautious despite optimistic data + + Analysts remain cautious despite optimistic data +

    @@ -8866,7 +9370,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i

    - Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits + + Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits +

    - US digital start-up to scale up EU operations with new Dublin branch + + US digital start-up to scale up EU operations with new Dublin branch +

    - Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely + + Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely +

    - Sterling has experienced several periods of volatility in the past 48 years + + Sterling has experienced several periods of volatility in the past 48 years +

    - Taoiseach says he always believed a deal this month was unlikely + + Taoiseach says he always believed a deal this month was unlikely +

    - While ministers haggle, company bosses press the button on expensive no-deal planning + + While ministers haggle, company bosses press the button on expensive no-deal planning +

    - MoD restarts competition despite industry’s concerns over budget and timing + + MoD restarts competition despite industry’s concerns over budget and timing +

    - Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources + + Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources +

    - As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely + + As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely +

    - UK and EU consider extending transition deal to defuse dispute over Irish border backstop + + UK and EU consider extending transition deal to defuse dispute over Irish border backstop +

    - Quarterly survey shows more companies now believe Brexit will damage their business than help them + + Quarterly survey shows more companies now believe Brexit will damage their business than help them +

    - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border + + In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border +

    - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth + + Governments should be more concerned about SMEs: if supply chains falter, so will economic growth +

    - White House makes London a priority ‘as soon as it is ready’ after Brexit + + White House makes London a priority ‘as soon as it is ready’ after Brexit +

    - Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ + + Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ +

    - Speaker John Bercow should take responsibility — and go now + + Speaker John Bercow should take responsibility — and go now +

    - May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ + + May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ +

    - Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself + + Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself +

    - Universal credit system not expected to be fully operational until December 2023 + + Universal credit system not expected to be fully operational until December 2023 +

    - Analysts remain cautious despite optimistic data + + Analysts remain cautious despite optimistic data +

    @@ -11010,7 +11654,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false

    - Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits + + Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits +

    - US digital start-up to scale up EU operations with new Dublin branch + + US digital start-up to scale up EU operations with new Dublin branch +

    - Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely + + Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely +

    - Sterling has experienced several periods of volatility in the past 48 years + + Sterling has experienced several periods of volatility in the past 48 years +

    - Taoiseach says he always believed a deal this month was unlikely + + Taoiseach says he always believed a deal this month was unlikely +

    - While ministers haggle, company bosses press the button on expensive no-deal planning + + While ministers haggle, company bosses press the button on expensive no-deal planning +

    - MoD restarts competition despite industry’s concerns over budget and timing + + MoD restarts competition despite industry’s concerns over budget and timing +

    - Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources + + Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources +

    Date: Thu, 25 Apr 2019 14:48:33 +0100 Subject: [PATCH 037/760] move release guidelines into docs, fixes #215 --- contribution.md | 2 +- docs/components/overview.md | 2 +- release-guidelines.md => docs/components/release-guidelines.md | 0 docs/get-started/what-is-x-dash.md | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename release-guidelines.md => docs/components/release-guidelines.md (100%) diff --git a/contribution.md b/contribution.md index 37b51000f..fbeec9021 100644 --- a/contribution.md +++ b/contribution.md @@ -60,7 +60,7 @@ Please do! All of the code in `x-dash` is peer-reviewed by members of The App an If you're thinking of opening a pull request that adds a feature, you'll save yourself some time and effort if you [discuss it in a feature request first](#requesting-features). The review is guaranteed to go more smoothly if we've chatted about it beforehand. - ### Check the workflow and release guidelines - The project follows a scheduled release workflow so we encourage the separation of stable, development, and experimental code. See the [Git workflow](#git-workflow) and the [release guidelines](release-guidelines.md) for more information. + The project follows a scheduled release workflow so we encourage the separation of stable, development, and experimental code. See the [Git workflow](#git-workflow) and the [release guidelines](docs/components/release-guidelines.md) for more information. - ### Update the documentation The user documentation should be kept up to date with any changes made. Use inline code comments as developer documentation, focusing more on _why_ your code does something than _what_ it's doing. diff --git a/docs/components/overview.md b/docs/components/overview.md index 01f63ecdf..dcd41db11 100644 --- a/docs/components/overview.md +++ b/docs/components/overview.md @@ -58,4 +58,4 @@ In addition it is encouraged to write unit tests for interactive or complex comp All x-dash components and packages will be published on the [npm registry] under the `@financial-times` organisation. Components in the `master` or current `development` branches of x-dash will all be published concurrently with the same version number. Experimental components may be published with an unstable version number using a [prerelease tag]. [npm registry]: https://www.npmjs.com/ -[prerelease tag]: https://github.com/Financial-Times/x-dash/blob/master/release-guidelines.md +[prerelease tag]: ./release-guidelines.md diff --git a/release-guidelines.md b/docs/components/release-guidelines.md similarity index 100% rename from release-guidelines.md rename to docs/components/release-guidelines.md diff --git a/docs/get-started/what-is-x-dash.md b/docs/get-started/what-is-x-dash.md index 89d1538c1..756745e7e 100644 --- a/docs/get-started/what-is-x-dash.md +++ b/docs/get-started/what-is-x-dash.md @@ -30,7 +30,7 @@ With x-dash we have introduced a [monorepo] project structure, new [contribution [monorepo]: https://en.wikipedia.org/wiki/Monorepo [contribution guidelines]: https://github.com/Financial-Times/x-dash/blob/master/contribution.md -[release process]: https://github.com/Financial-Times/x-dash/blob/master/release-guidelines.md +[release process]: ../components/release-guidelines.md ## Does x-dash mean we can use React? From 439b753d2fd5cc27c2168518a2df1ca5459c04a5 Mon Sep 17 00:00:00 2001 From: bren brightwell Date: Mon, 29 Apr 2019 14:57:47 +0100 Subject: [PATCH 038/760] Update pull_request_template.md --- .github/pull_request_template.md | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a48dd077b..3f9be5d5a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,12 +1,21 @@ -If this is your first `x-dash` pull request please familiarise yourself with the contribution guide before submitting. - -**TL;DR** - - - Discuss features first - - Update the documentation - - No hacks, experiments or temporary workarounds - - Reviewers are empowered to say no - - Reference other issues - - Update affected stories and snapshots - - Follow the code style - - Decide on a version (major, minor, or patch) +If this is your first `x-dash` pull request please familiarise yourself with the [contribution guide](https://github.com/Financial-Times/x-dash/blob/master/contribution.md) before submitting. + +## If you're creating a component: + +- Add the `Component` label to this Pull Request +- If this will be a long-lived PR, consider using smaller PRs targeting this branch for individual features, so your team can review them without involving x-dash maintainers + - If you're using this workflow, create a Label and a Project for your component and ensure all small PRs are attached to them. Add the Project to the [Components board](https://github.com/Financial-Times/x-dash/projects/4) + - put a link to this Pull Request in the Project description + - set the Project to `Automated kanban with reviews`, but remove the `To Do` column + - If you're not using this workflow, add this Pull Request to the [Components board](https://github.com/Financial-Times/x-dash/projects/4). + +## + +- Discuss features first +- Update the documentation +- No hacks, experiments or temporary workarounds +- Reviewers are empowered to say no +- Reference other issues +- Update affected stories and snapshots +- Follow the code style +- Decide on a version (major, minor, or patch) From f3fc2508c1eafdce2fdd00f4938fadbd9667a076 Mon Sep 17 00:00:00 2001 From: Sami Triki Date: Thu, 2 May 2019 14:35:03 +0100 Subject: [PATCH 039/760] Remove extra space after title text --- components/x-teaser/src/Title.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/x-teaser/src/Title.jsx b/components/x-teaser/src/Title.jsx index f5cb3a23a..17825cd6b 100644 --- a/components/x-teaser/src/Title.jsx +++ b/components/x-teaser/src/Title.jsx @@ -14,7 +14,6 @@ export default ({ title, altTitle, headlineTesting, relativeUrl, url, indicators className: 'js-teaser-heading-link', }}> {displayTitle} - {' '} {indicators && indicators.accessLevel === 'premium' ? ( From ada8ef22525521db41f1b0b654ada5cbb4a7026c Mon Sep 17 00:00:00 2001 From: bren Date: Thu, 2 May 2019 16:44:37 +0100 Subject: [PATCH 040/760] update snapshots --- .../__snapshots__/snapshots.test.js.snap | 72 ------------------- 1 file changed, 72 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 2248d5399..b01a0ea55 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -68,7 +68,6 @@ exports[`@financial-times/x-teaser renders a Hero Article x-teaser 1`] = ` href="#" > Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    @@ -208,7 +206,6 @@ exports[`@financial-times/x-teaser renders a Hero Opinion Piece x-teaser 1`] = ` href="#" > Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    @@ -339,7 +335,6 @@ exports[`@financial-times/x-teaser renders a Hero Paid Post x-teaser 1`] = ` href="#" > Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    @@ -705,7 +697,6 @@ exports[`@financial-times/x-teaser renders a HeroNarrow Article x-teaser 1`] = ` href="#" > Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    @@ -1518,7 +1500,6 @@ exports[`@financial-times/x-teaser renders a HeroOverlay Opinion Piece x-teaser href="#" > Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    @@ -1649,7 +1629,6 @@ exports[`@financial-times/x-teaser renders a HeroOverlay Paid Post x-teaser 1`] href="#" > Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    @@ -2015,7 +1991,6 @@ exports[`@financial-times/x-teaser renders a HeroVideo Article x-teaser 1`] = ` href="#" > Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    @@ -2155,7 +2129,6 @@ exports[`@financial-times/x-teaser renders a HeroVideo Opinion Piece x-teaser 1` href="#" > Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    @@ -2286,7 +2258,6 @@ exports[`@financial-times/x-teaser renders a HeroVideo Paid Post x-teaser 1`] = href="#" > Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    @@ -2652,7 +2620,6 @@ exports[`@financial-times/x-teaser renders a Large Article x-teaser 1`] = ` href="#" > Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    @@ -3465,7 +3423,6 @@ exports[`@financial-times/x-teaser renders a Small Opinion Piece x-teaser 1`] = href="#" > Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    @@ -3596,7 +3552,6 @@ exports[`@financial-times/x-teaser renders a Small Paid Post x-teaser 1`] = ` href="#" > Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    @@ -3962,7 +3914,6 @@ exports[`@financial-times/x-teaser renders a SmallHeavy Article x-teaser 1`] = ` href="#" > Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    Inside charity fundraiser where hostesses are put on show -

    The royal wedding -

    Anti-Semitism and the threat of identity politics -

    Why so little has changed since the crash -

    Why eSports companies are on a winning streak -

    Who sets the internet standards? -

    Inside charity fundraiser where hostesses are put on show -

    FT View: Donald Trump, man of steel -

    Date: Tue, 7 May 2019 16:31:36 +0100 Subject: [PATCH 041/760] set up storybook to run for heroku - allow setting custom port - heroku boilerplate files --- Procfile | 1 + app.json | 9 +++++++++ tools/x-storybook/package.json | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Procfile create mode 100644 app.json diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..c94086719 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: STORYBOOK_PORT=$PORT npm run start-storybook \ No newline at end of file diff --git a/app.json b/app.json new file mode 100644 index 000000000..6eacbff49 --- /dev/null +++ b/app.json @@ -0,0 +1,9 @@ +{ + "env": {}, + "formation": { + "web": { + "quantity": 1, + "size": "Standard-1X" + } + } +} diff --git a/tools/x-storybook/package.json b/tools/x-storybook/package.json index 23decb35c..aa1899417 100644 --- a/tools/x-storybook/package.json +++ b/tools/x-storybook/package.json @@ -5,7 +5,7 @@ "description": "", "main": "register-components.js", "scripts": { - "start": "start-storybook -p 9001 -c .storybook -s static -h local.ft.com", + "start": "start-storybook -p ${STORYBOOK_PORT:-9001} -c .storybook -s static -h local.ft.com", "build": "build-storybook -c .storybook -o dist/storybook -s static" }, "keywords": [], From b7101d5edd3d8d154bdbd9103aaef39455677510 Mon Sep 17 00:00:00 2001 From: bren Date: Tue, 7 May 2019 16:32:18 +0100 Subject: [PATCH 042/760] build components on heroku postbuild --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d1f5b1608..53e7195e9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", "start-storybook": "(cd tools/x-storybook && npm start)", - "start-docs": "(cd tools/x-docs && npm start)" + "start-docs": "(cd tools/x-docs && npm start)", + "heroku-postbuild": "npm run build" }, "dependencies": { "@financial-times/athloi": "^1.0.0-beta.19", From fb30942deeed6630151deb2a554692928ab426c2 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 16:37:15 +0100 Subject: [PATCH 043/760] Configure Storybook at the repository root - Adds Storybook v5 as a top-level dependency - Copies configuration from subdirectory to .storybook folder in repo root - References included components by path specifier instead of package name --- .storybook/.browserslistrc | 4 ++ .storybook/addons.js | 2 + .storybook/build-service.js | 47 ++++++++++++++++++ .storybook/build-story.js | 81 +++++++++++++++++++++++++++++++ .storybook/config.js | 9 ++++ .storybook/preview-head.html | 27 +++++++++++ .storybook/register-components.js | 7 +++ .storybook/webpack.config.js | 49 +++++++++++++++++++ package.json | 31 +++++++----- 9 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 .storybook/.browserslistrc create mode 100644 .storybook/addons.js create mode 100644 .storybook/build-service.js create mode 100644 .storybook/build-story.js create mode 100644 .storybook/config.js create mode 100644 .storybook/preview-head.html create mode 100644 .storybook/register-components.js create mode 100644 .storybook/webpack.config.js diff --git a/.storybook/.browserslistrc b/.storybook/.browserslistrc new file mode 100644 index 000000000..a4eb887f0 --- /dev/null +++ b/.storybook/.browserslistrc @@ -0,0 +1,4 @@ +last 2 Chrome versions +last 2 FF versions +last 2 Edge versions +Safari >= 12 diff --git a/.storybook/addons.js b/.storybook/addons.js new file mode 100644 index 000000000..41bc2666b --- /dev/null +++ b/.storybook/addons.js @@ -0,0 +1,2 @@ +import '@storybook/addon-knobs/register' +import '@storybook/addon-viewport/register' diff --git a/.storybook/build-service.js b/.storybook/build-service.js new file mode 100644 index 000000000..bbeb441a9 --- /dev/null +++ b/.storybook/build-service.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; + +function buildServiceUrl(deps, type) { + const modules = Object.keys(deps).map((i) => `${i}@${deps[i]}`).join(','); + return `https://www.ft.com/__origami/service/build/v2/bundles/${type}?modules=${modules}`; +} + +class BuildService extends React.Component { + constructor(props) { + super(props); + this.initialised = []; + } + + componentDidUpdate() { + if (window.hasOwnProperty('Origami')) { + for (const component in Origami) { + if (typeof Origami[component].init === 'function') { + const instance = Origami[component].init(); + this.initialised.concat(instance); + } + } + } + } + + componentWillUnmount() { + this.initialised.forEach((instance) => { + if (typeof instance.destroy === 'function') { + instance.destroy(); + } + }); + } + + render() { + const js = buildServiceUrl(this.props.dependencies, 'js'); + const css = buildServiceUrl(this.props.dependencies, 'css'); + + return ( + + + + + ); + } +} + +export default BuildService; diff --git a/.storybook/build-story.js b/.storybook/build-story.js new file mode 100644 index 000000000..1c7c49bba --- /dev/null +++ b/.storybook/build-story.js @@ -0,0 +1,81 @@ +import React from 'react'; +import BuildService from './build-service'; +import { storiesOf } from '@storybook/react'; +import * as knobsAddon from '@storybook/addon-knobs'; +import { Helmet } from 'react-helmet'; +import path from 'path'; +import fetchMock from 'fetch-mock'; + +const defaultKnobs = () => ({}); + +/** + * Create Props + * @param {{ [key: string]: any }} defaultData + * @param {String[]} allowedKnobs + * @param {Function} hydrateKnobs + */ +function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs) { + // Inject knobs add-on into given dependency container + const knobs = hydrateKnobs(defaultData, knobsAddon); + // Mix the available knob props into default data + const mixedProps = { ...defaultData, ...knobs }; + + if (allowedKnobs.length === 0) { + return mixedProps; + } + + return allowedKnobs.reduce((map, prop) => { + if (mixedProps.hasOwnProperty(prop)) { + const value = mixedProps[prop]; + + // Knobs are functions which need calling to register them + if (typeof value === 'function') { + map[prop] = value(); + } else { + map[prop] = value; + } + } + + return map; + }, {}); +} + +/** + * Build Story + * @param {String} name + * @param {{ [key: string]: string }} dependencies + * @param {Function} Component + * @param {Function} knobs + * @param {{ title: String, data: {}, knobs: String[], m: module }} story + */ +function buildStory({ package: pkg, dependencies, component: Component, knobs, story }) { + const name = path.basename(pkg.name); + const storybook = storiesOf(name, story.m); + + storybook.addDecorator(knobsAddon.withKnobs); + + storybook.add(story.title, () => { + const props = createProps(story.data, story.knobs, knobs); + + if (story.fetchMock) { + fetchMock.restore(); // to isolate the mocks to each story + story.fetchMock(fetchMock); + } + + return ( +

    + {dependencies && } + {pkg.style && ( + + + + )} + +
    + ); + }); + + return storybook; +} + +export default buildStory; diff --git a/.storybook/config.js b/.storybook/config.js new file mode 100644 index 000000000..d593fe2bf --- /dev/null +++ b/.storybook/config.js @@ -0,0 +1,9 @@ +import { configure } from '@storybook/react'; +import buildStory from './build-story'; +import * as components from './register-components'; + +configure(() => { + components.forEach(({ stories, ...data }) => { + stories.forEach((story) => buildStory({ story, ...data })); + }); +}, module); diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 000000000..140b82e06 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/.storybook/register-components.js b/.storybook/register-components.js new file mode 100644 index 000000000..0d2bce45e --- /dev/null +++ b/.storybook/register-components.js @@ -0,0 +1,7 @@ +const components = [ + require('../components/x-teaser/stories'), + require('../components/x-increment/stories'), + require('../components/x-styling-demo/stories'), +]; + +module.exports = components; diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js new file mode 100644 index 000000000..90cdaf2c8 --- /dev/null +++ b/.storybook/webpack.config.js @@ -0,0 +1,49 @@ +// This configuration extends the existing Storybook Webpack config. +// See https://storybook.js.org/configurations/custom-webpack-config/ for more info. + +const path = require('path'); +const fs = require('fs'); +const xEngine = require('../packages/x-engine/src/webpack'); +const CopyPlugin = require('copy-webpack-plugin'); +const WritePlugin = require('write-file-webpack-plugin'); + +const excludePaths = [/node_modules/, /dist/]; + +const cssCopy = fs.readdirSync(path.resolve('components')).reduce((mains, component) => { + const componentPkg = path.resolve('components', component, 'package.json'); + + if (fs.existsSync(componentPkg)) { + const pkg = require(componentPkg); + + if (pkg.style) { + const styleResolved = path.resolve('components', component, pkg.style); + + return mains.concat({ + from: styleResolved, + to: path.resolve(__dirname, '../static/components', path.basename(pkg.name), pkg.style) + }); + } + } + + return mains; +}, []); + +module.exports = ({ config }) => { + // HACK: extend existing JS rule to ensure all dependencies are correctly ignored + // from Babel transpilation. + // https://github.com/storybooks/storybook/issues/3346#issuecomment-459439438 + const jsRule = config.module.rules.find((rule) => rule.test.test('.jsx')); + jsRule.exclude = excludePaths; + + // HACK: Instruct Babel to check module type before injecting Core JS polyfills + // https://github.com/i-like-robots/broken-webpack-bundle-test-case + const babelConfig = jsRule.use.find(({ loader }) => loader === 'babel-loader'); + babelConfig.options.sourceType = 'unambiguous'; + + // HACK: Ensure we only bundle one instance of React + config.resolve.alias.react = require.resolve('react'); + + config.plugins.push(xEngine(), new CopyPlugin(cssCopy), new WritePlugin()); + + return config; +}; diff --git a/package.json b/package.json index 53e7195e9..49802b646 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,21 @@ "test": "npm run lint && npm run jest", "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", - "start-storybook": "(cd tools/x-storybook && npm start)", + "start-storybook": "start-storybook -p 9001 -c .storybook -s static -h local.ft.com", + "build-storybook": "build-storybook -c .storybook -o dist/storybook -s static", "start-docs": "(cd tools/x-docs && npm start)", "heroku-postbuild": "npm run build" }, - "dependencies": { + "devDependencies": { + "@babel/core": "^7.1.5", "@financial-times/athloi": "^1.0.0-beta.19", + "@storybook/addon-knobs": "^5.0.10", + "@storybook/addon-viewport": "^5.0.10", + "@storybook/react": "^5.0.10", "acorn": "^6.0.2", "acorn-jsx": "^5.0.0", + "babel-loader": "^8.0.4", + "copy-webpack-plugin": "^4.6.0", "eslint": "^5.1.0", "eslint-config-prettier": "^2.9.0", "eslint-plugin-jest": "^21.17.0", @@ -23,24 +30,24 @@ "eslint-plugin-react": "^7.10.0", "espree": "^4.1.0", "fetch-mock": "^6.5.2", + "jest": "^24.7.1", + "node-sass": "^4.11.0", "react": "^16.3.1", - "react-test-renderer": "^16.3.1" + "react-helmet": "^5.2.0", + "react-test-renderer": "^16.3.1", + "sass-loader": "^7.1.0", + "style-loader": "^0.23.1", + "write-file-webpack-plugin": "^4.5.0" }, "x-dash": { "engine": { - "server": { - "runtime": "react", - "factory": "createElement", - "component": "Component" - } + "browser": "react", + "server": "react" } }, "workspaces": [ "components/*", "packages/*", "tools/*" - ], - "devDependencies": { - "jest": "^24.7.1" - } + ] } From 092d7e47338341f0e419f8eaa4afaf3dd6384376 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 16:39:34 +0100 Subject: [PATCH 044/760] Remove old Storybook implementation in tools directory --- package.json | 2 +- tools/x-storybook/.gitignore | 1 - tools/x-storybook/.storybook/addons.js | 2 - tools/x-storybook/.storybook/build-service.js | 47 ----------- tools/x-storybook/.storybook/build-story.js | 81 ------------------- tools/x-storybook/.storybook/config.js | 9 --- .../x-storybook/.storybook/preview-head.html | 27 ------- .../x-storybook/.storybook/webpack.config.js | 44 ---------- tools/x-storybook/package.json | 38 --------- tools/x-storybook/register-components.js | 7 -- .../x-storybook/static/components/.gitignore | 2 - 11 files changed, 1 insertion(+), 259 deletions(-) delete mode 100644 tools/x-storybook/.gitignore delete mode 100644 tools/x-storybook/.storybook/addons.js delete mode 100644 tools/x-storybook/.storybook/build-service.js delete mode 100644 tools/x-storybook/.storybook/build-story.js delete mode 100644 tools/x-storybook/.storybook/config.js delete mode 100644 tools/x-storybook/.storybook/preview-head.html delete mode 100644 tools/x-storybook/.storybook/webpack.config.js delete mode 100644 tools/x-storybook/package.json delete mode 100644 tools/x-storybook/register-components.js delete mode 100644 tools/x-storybook/static/components/.gitignore diff --git a/package.json b/package.json index 49802b646..4fac0fffb 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "test": "npm run lint && npm run jest", "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", - "start-storybook": "start-storybook -p 9001 -c .storybook -s static -h local.ft.com", + "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -c .storybook -s static -h local.ft.com", "build-storybook": "build-storybook -c .storybook -o dist/storybook -s static", "start-docs": "(cd tools/x-docs && npm start)", "heroku-postbuild": "npm run build" diff --git a/tools/x-storybook/.gitignore b/tools/x-storybook/.gitignore deleted file mode 100644 index 16d3c4dbb..000000000 --- a/tools/x-storybook/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.cache diff --git a/tools/x-storybook/.storybook/addons.js b/tools/x-storybook/.storybook/addons.js deleted file mode 100644 index eb2cbb9ce..000000000 --- a/tools/x-storybook/.storybook/addons.js +++ /dev/null @@ -1,2 +0,0 @@ -import '@storybook/addon-knobs/register'; -import '@storybook/addon-viewport/register'; diff --git a/tools/x-storybook/.storybook/build-service.js b/tools/x-storybook/.storybook/build-service.js deleted file mode 100644 index bbeb441a9..000000000 --- a/tools/x-storybook/.storybook/build-service.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { Helmet } from 'react-helmet'; - -function buildServiceUrl(deps, type) { - const modules = Object.keys(deps).map((i) => `${i}@${deps[i]}`).join(','); - return `https://www.ft.com/__origami/service/build/v2/bundles/${type}?modules=${modules}`; -} - -class BuildService extends React.Component { - constructor(props) { - super(props); - this.initialised = []; - } - - componentDidUpdate() { - if (window.hasOwnProperty('Origami')) { - for (const component in Origami) { - if (typeof Origami[component].init === 'function') { - const instance = Origami[component].init(); - this.initialised.concat(instance); - } - } - } - } - - componentWillUnmount() { - this.initialised.forEach((instance) => { - if (typeof instance.destroy === 'function') { - instance.destroy(); - } - }); - } - - render() { - const js = buildServiceUrl(this.props.dependencies, 'js'); - const css = buildServiceUrl(this.props.dependencies, 'css'); - - return ( - - - - - ); - } -} - -export default BuildService; diff --git a/tools/x-storybook/.storybook/build-story.js b/tools/x-storybook/.storybook/build-story.js deleted file mode 100644 index 1c7c49bba..000000000 --- a/tools/x-storybook/.storybook/build-story.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import BuildService from './build-service'; -import { storiesOf } from '@storybook/react'; -import * as knobsAddon from '@storybook/addon-knobs'; -import { Helmet } from 'react-helmet'; -import path from 'path'; -import fetchMock from 'fetch-mock'; - -const defaultKnobs = () => ({}); - -/** - * Create Props - * @param {{ [key: string]: any }} defaultData - * @param {String[]} allowedKnobs - * @param {Function} hydrateKnobs - */ -function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs) { - // Inject knobs add-on into given dependency container - const knobs = hydrateKnobs(defaultData, knobsAddon); - // Mix the available knob props into default data - const mixedProps = { ...defaultData, ...knobs }; - - if (allowedKnobs.length === 0) { - return mixedProps; - } - - return allowedKnobs.reduce((map, prop) => { - if (mixedProps.hasOwnProperty(prop)) { - const value = mixedProps[prop]; - - // Knobs are functions which need calling to register them - if (typeof value === 'function') { - map[prop] = value(); - } else { - map[prop] = value; - } - } - - return map; - }, {}); -} - -/** - * Build Story - * @param {String} name - * @param {{ [key: string]: string }} dependencies - * @param {Function} Component - * @param {Function} knobs - * @param {{ title: String, data: {}, knobs: String[], m: module }} story - */ -function buildStory({ package: pkg, dependencies, component: Component, knobs, story }) { - const name = path.basename(pkg.name); - const storybook = storiesOf(name, story.m); - - storybook.addDecorator(knobsAddon.withKnobs); - - storybook.add(story.title, () => { - const props = createProps(story.data, story.knobs, knobs); - - if (story.fetchMock) { - fetchMock.restore(); // to isolate the mocks to each story - story.fetchMock(fetchMock); - } - - return ( -
    - {dependencies && } - {pkg.style && ( - - - - )} - -
    - ); - }); - - return storybook; -} - -export default buildStory; diff --git a/tools/x-storybook/.storybook/config.js b/tools/x-storybook/.storybook/config.js deleted file mode 100644 index d2a527fab..000000000 --- a/tools/x-storybook/.storybook/config.js +++ /dev/null @@ -1,9 +0,0 @@ -import { configure } from '@storybook/react'; -import buildStory from './build-story'; -import * as components from '../register-components'; - -configure(() => { - components.forEach(({ stories, ...data }) => { - stories.forEach((story) => buildStory({ story, ...data })); - }); -}, module); diff --git a/tools/x-storybook/.storybook/preview-head.html b/tools/x-storybook/.storybook/preview-head.html deleted file mode 100644 index 140b82e06..000000000 --- a/tools/x-storybook/.storybook/preview-head.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - \ No newline at end of file diff --git a/tools/x-storybook/.storybook/webpack.config.js b/tools/x-storybook/.storybook/webpack.config.js deleted file mode 100644 index 4ea542d63..000000000 --- a/tools/x-storybook/.storybook/webpack.config.js +++ /dev/null @@ -1,44 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const findUp = require('find-up'); -const xEngine = require('@financial-times/x-engine/src/webpack'); -const CopyPlugin = require('copy-webpack-plugin'); -const WritePlugin = require('write-file-webpack-plugin'); - -// TODO: Find a less obtuse heuristic? -const repoBase = path.dirname(findUp.sync('makefile')); - -const cssCopy = fs.readdirSync( - path.resolve(repoBase, 'components') -).reduce((mains, component) => { - const componentPkg = path.resolve(repoBase, 'components', component, 'package.json'); - - if(fs.existsSync(componentPkg)) { - const pkg = require(componentPkg); - - if(pkg.style) { - const styleResolved = path.resolve(repoBase, 'components', component, pkg.style); - - return mains.concat({ - from: styleResolved, - to: path.resolve(__dirname, '../static/components', path.basename(pkg.name), pkg.style), - }); - } - } - - return mains; -}, []); - -module.exports = { - resolve: { - alias: { - '@storybook/addons': require.resolve('@storybook/addons'), - 'react': require.resolve('react'), - } - }, - plugins: [ - xEngine(), - new CopyPlugin(cssCopy), - new WritePlugin(), - ], -}; diff --git a/tools/x-storybook/package.json b/tools/x-storybook/package.json deleted file mode 100644 index aa1899417..000000000 --- a/tools/x-storybook/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@financial-times/x-storybook", - "private": true, - "version": "0.0.0", - "description": "", - "main": "register-components.js", - "scripts": { - "start": "start-storybook -p ${STORYBOOK_PORT:-9001} -c .storybook -s static -h local.ft.com", - "build": "build-storybook -c .storybook -o dist/storybook -s static" - }, - "keywords": [], - "author": "", - "license": "ISC", - "x-dash": { - "engine": { - "browser": "react" - } - }, - "devDependencies": { - "@babel/core": "^7.1.5", - "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-increment": "file:../../components/x-increment", - "@financial-times/x-styling-demo": "file:../../components/x-styling-demo", - "@financial-times/x-teaser": "file:../../components/x-teaser", - "@storybook/addon-knobs": "^4.0.4", - "@storybook/addon-viewport": "^4.0.4", - "@storybook/addons": "^4.0.4", - "@storybook/cli": "^4.0.4", - "@storybook/react": "^4.0.4", - "babel-loader": "^8.0.4", - "copy-webpack-plugin": "^4.6.0", - "react": "^16.3.0", - "react-dom": "^16.3.0", - "react-helmet": "^5.2.0", - "style-loader": "^0.23.0", - "write-file-webpack-plugin": "^4.4.0" - } -} diff --git a/tools/x-storybook/register-components.js b/tools/x-storybook/register-components.js deleted file mode 100644 index be0649af3..000000000 --- a/tools/x-storybook/register-components.js +++ /dev/null @@ -1,7 +0,0 @@ -const components = [ - require('@financial-times/x-teaser/stories'), - require('@financial-times/x-increment/stories'), - require('@financial-times/x-styling-demo/stories'), -]; - -module.exports = components; diff --git a/tools/x-storybook/static/components/.gitignore b/tools/x-storybook/static/components/.gitignore deleted file mode 100644 index d6b7ef32c..000000000 --- a/tools/x-storybook/static/components/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore From 58c678b915d0a21acc1b6fdca5c7fe44f589ed43 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 16:40:36 +0100 Subject: [PATCH 045/760] Remove custom Storybook caching behaviour from Circle configuration This is now handled by the existing "root" cache and cache key. --- .circleci/config.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 129b2d674..59ca3a95e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,10 +28,6 @@ references: keys: - cache-docs-v1-{{ .Branch }}-{{ checksum "./tools/x-docs/package.json" }} - cache_keys_storybook: &cache_keys_storybook - keys: - - cache-storybook-v1-{{ .Branch }}-{{ checksum "./tools/x-storybook/package.json" }} - # # Cache creation # @@ -47,12 +43,6 @@ references: paths: - ./tools/x-docs/node_modules/ - create_cache_storybook: &create_cache_storybook - save_cache: - key: cache-storybook-v1-{{ .Branch }}-{{ checksum "./tools/x-storybook/package.json" }} - paths: - - ./tools/x-storybook/node_modules/ - # # Cache restoration # @@ -64,10 +54,6 @@ references: restore_cache: <<: *cache_keys_docs - restore_cache_storybook: &restore_cache_storybook - restore_cache: - <<: *cache_keys_storybook - # # Filters # @@ -108,7 +94,6 @@ jobs: command: git clone --depth 1 git@github.com:Financial-Times/next-ci-shared-helpers.git .circleci/shared-helpers - *restore_cache_root - *restore_cache_docs - - *restore_cache_storybook - run: name: Install project dependencies command: make install @@ -117,7 +102,6 @@ jobs: command: make build - *create_cache_root - *create_cache_docs - - *create_cache_storybook - persist_to_workspace: root: *workspace_root paths: From 2e9cee845000ae01433055c001da6ec255687ff1 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 16:42:00 +0100 Subject: [PATCH 046/760] Remove references to x-storybook from documentation --- docs/get-started/working-with-x-dash.md | 4 +--- tools/x-docs/package.json | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/get-started/working-with-x-dash.md b/docs/get-started/working-with-x-dash.md index 867be5f91..c57d52db9 100644 --- a/docs/get-started/working-with-x-dash.md +++ b/docs/get-started/working-with-x-dash.md @@ -19,9 +19,7 @@ The repository groups related code together in directories. UI components are st │ ├ readme.md │ └ package.json ├ tools/ -│ ├ x-docs/ -│ │ └ package.json -│ └ x-storybook/ +│ └ x-docs/ │ └ package.json ├ readme.md └ package.json diff --git a/tools/x-docs/package.json b/tools/x-docs/package.json index f072a35b4..b36429da0 100644 --- a/tools/x-docs/package.json +++ b/tools/x-docs/package.json @@ -11,7 +11,6 @@ "license": "ISC", "devDependencies": { "@financial-times/x-logo": "file:../../packages/x-logo", - "@financial-times/x-storybook": "file:../x-storybook", "case": "^1.5.5", "gatsby": "^2.0.11", "gatsby-remark-autolink-headers": "^2.0.6", From 6c00a618474f55c075e2d3b6f100ea720b2ddad1 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 16:45:20 +0100 Subject: [PATCH 047/760] Update Storybook Webpack configuration to copy static files into a directory within the Storybook folder --- .storybook/.gitignore | 1 + .storybook/webpack.config.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .storybook/.gitignore diff --git a/.storybook/.gitignore b/.storybook/.gitignore new file mode 100644 index 000000000..980c85122 --- /dev/null +++ b/.storybook/.gitignore @@ -0,0 +1 @@ +static/ diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 90cdaf2c8..702d61362 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -20,7 +20,7 @@ const cssCopy = fs.readdirSync(path.resolve('components')).reduce((mains, compon return mains.concat({ from: styleResolved, - to: path.resolve(__dirname, '../static/components', path.basename(pkg.name), pkg.style) + to: path.resolve(__dirname, 'static/components', path.basename(pkg.name), pkg.style) }); } } diff --git a/package.json b/package.json index 4fac0fffb..b34750450 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "test": "npm run lint && npm run jest", "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", - "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -c .storybook -s static -h local.ft.com", + "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", "build-storybook": "build-storybook -c .storybook -o dist/storybook -s static", "start-docs": "(cd tools/x-docs && npm start)", "heroku-postbuild": "npm run build" From acd5dd7afe0e8cb857b06f8a82cf78a7d3c200ff Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 16:56:08 +0100 Subject: [PATCH 048/760] Update the Storybook build npm script to direct output into the Storybook directory --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b34750450..fea52f240 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", - "build-storybook": "build-storybook -c .storybook -o dist/storybook -s static", + "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", "start-docs": "(cd tools/x-docs && npm start)", "heroku-postbuild": "npm run build" }, From 4b080244951ccbf3dcd9344f5160c0872ced3691 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 17:05:16 +0100 Subject: [PATCH 049/760] Update the Gatsby static file symlink to the new Storybook output location --- tools/x-docs/static/storybook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/x-docs/static/storybook b/tools/x-docs/static/storybook index 3e0bb31d9..be2899010 120000 --- a/tools/x-docs/static/storybook +++ b/tools/x-docs/static/storybook @@ -1 +1 @@ -../node_modules/@financial-times/x-storybook/dist/storybook/ \ No newline at end of file +../../../.storybook/dist/ \ No newline at end of file From a27a7616b776017a81da5a323e5121c35928a3a0 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 17:05:50 +0100 Subject: [PATCH 050/760] Build Storybook as part of the top-level 'build' script so it builds on CI as before --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fea52f240..6637695e5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": true, "scripts": { "clean": "git clean -fxdi", - "build": "athloi run build", + "build": "athloi run build && npm run build-storybook", "jest": "jest -c jest.config.js", "pretest": "npm run build", "test": "npm run lint && npm run jest", From 0b0465d23dd72a596e77116c8d0933de6200fce0 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 24 Apr 2019 17:12:18 +0100 Subject: [PATCH 051/760] Remove unnecessary .gitignore from Storybook directory --- .storybook/.gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .storybook/.gitignore diff --git a/.storybook/.gitignore b/.storybook/.gitignore deleted file mode 100644 index 980c85122..000000000 --- a/.storybook/.gitignore +++ /dev/null @@ -1 +0,0 @@ -static/ From 71b4292af89735b3792c2d731519048ac3dcfab3 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 12:34:24 +0100 Subject: [PATCH 052/760] fix the symlink to storybook --- tools/x-docs/static/storybook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/x-docs/static/storybook b/tools/x-docs/static/storybook index be2899010..541a6f86c 120000 --- a/tools/x-docs/static/storybook +++ b/tools/x-docs/static/storybook @@ -1 +1 @@ -../../../.storybook/dist/ \ No newline at end of file +../../../dist/storybook/ \ No newline at end of file From c61ef2fbb58056186ac9d39fce3fa6fa38ab5d60 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 14:35:11 +0100 Subject: [PATCH 053/760] expose source files in packages - remove src folder from x-teaser npmignore - add source field to component package.json files --- components/x-increment/package.json | 1 + components/x-interaction/package.json | 1 + components/x-styling-demo/package.json | 1 + components/x-teaser/.npmignore | 1 - components/x-teaser/package.json | 1 + 5 files changed, 4 insertions(+), 1 deletion(-) diff --git a/components/x-increment/package.json b/components/x-increment/package.json index eba49cf3a..803fab6a1 100644 --- a/components/x-increment/package.json +++ b/components/x-increment/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "private": true, "description": "", + "source": "src/Increment.jsx", "main": "dist/Increment.cjs.js", "module": "dist/Increment.esm.js", "browser": "dist/Increment.es5.js", diff --git a/components/x-interaction/package.json b/components/x-interaction/package.json index 8bf33f778..613f6873a 100644 --- a/components/x-interaction/package.json +++ b/components/x-interaction/package.json @@ -2,6 +2,7 @@ "name": "@financial-times/x-interaction", "version": "0.0.0", "description": "This module enables you to write x-dash components that respond to events and change their own data.", + "source": "src/Interaction.jsx", "main": "dist/Interaction.cjs.js", "module": "dist/Interaction.esm.js", "browser": "dist/Interaction.es5.js", diff --git a/components/x-styling-demo/package.json b/components/x-styling-demo/package.json index bf0444556..c629102b6 100644 --- a/components/x-styling-demo/package.json +++ b/components/x-styling-demo/package.json @@ -2,6 +2,7 @@ "name": "@financial-times/x-styling-demo", "version": "0.0.0", "description": "", + "source": "src/Button.jsx", "main": "dist/Button.cjs.js", "browser": "dist/Button.es5.js", "module": "dist/Button.esm.js", diff --git a/components/x-teaser/.npmignore b/components/x-teaser/.npmignore index a31002f9a..714c4a0a3 100644 --- a/components/x-teaser/.npmignore +++ b/components/x-teaser/.npmignore @@ -1,3 +1,2 @@ -src/ stories/ rollup.config.js diff --git a/components/x-teaser/package.json b/components/x-teaser/package.json index 5852abacb..46f486bc5 100644 --- a/components/x-teaser/package.json +++ b/components/x-teaser/package.json @@ -2,6 +2,7 @@ "name": "@financial-times/x-teaser", "version": "0.0.0", "description": "This module provides templates for use with o-teaser. Teasers are used to present content.", + "source": "src/Teaser.jsx", "main": "dist/Teaser.cjs.js", "module": "dist/Teaser.esm.js", "browser": "dist/Teaser.es5.js", From febbb809c449935de0de3e5a4ac91168fe9bc829 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 17:32:44 +0100 Subject: [PATCH 054/760] use static buildpack, build storybook --- app.json | 13 +++++++++++-- package.json | 2 +- static.json | 3 +++ 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 static.json diff --git a/app.json b/app.json index 6eacbff49..801f51303 100644 --- a/app.json +++ b/app.json @@ -1,9 +1,18 @@ { - "env": {}, + "env": { + "NPM_CONFIG_PRODUCTION": { + "description": "don't prune devDependencies", + "value": "false" + } + }, "formation": { "web": { "quantity": 1, "size": "Standard-1X" } - } + }, + "buildpacks": [ + {"url": "https://github.com/heroku/heroku-buildpack-static.git"}, + {"url": "heroku/nodejs"} + ] } diff --git a/package.json b/package.json index 6637695e5..12ce59ec3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", "start-docs": "(cd tools/x-docs && npm start)", - "heroku-postbuild": "npm run build" + "heroku-postbuild": "make install && npm run build" }, "devDependencies": { "@babel/core": "^7.1.5", diff --git a/static.json b/static.json new file mode 100644 index 000000000..9e4c3e264 --- /dev/null +++ b/static.json @@ -0,0 +1,3 @@ +{ + "root": "dist/storybook" +} From b04a76a9cc5cf5b0c1e9d2f32c743d46ea578195 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 17:42:06 +0100 Subject: [PATCH 055/760] remove redundant procfile --- Procfile | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Procfile diff --git a/Procfile b/Procfile deleted file mode 100644 index c94086719..000000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: STORYBOOK_PORT=$PORT npm run start-storybook \ No newline at end of file From 2d98c64afad417bfd58d4b8d3a37446930feee14 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 17:42:29 +0100 Subject: [PATCH 056/760] i don't think actually we need to not prune devdependencies --- app.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app.json b/app.json index 801f51303..c5d18fd4c 100644 --- a/app.json +++ b/app.json @@ -1,10 +1,5 @@ { - "env": { - "NPM_CONFIG_PRODUCTION": { - "description": "don't prune devDependencies", - "value": "false" - } - }, + "env": {}, "formation": { "web": { "quantity": 1, From aab0bfc8563df2cde4e31ca92945345af9f643f4 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 17:51:15 +0100 Subject: [PATCH 057/760] pretty sure those prepare scripts are redundant, bc: - build runs before deploy on circle - build runs on postbuild on heroku --- components/x-increment/package.json | 1 - components/x-interaction/package.json | 1 - components/x-styling-demo/package.json | 1 - components/x-teaser/package.json | 1 - private/blueprints/component/package.json | 1 - 5 files changed, 5 deletions(-) diff --git a/components/x-increment/package.json b/components/x-increment/package.json index eba49cf3a..d40d68675 100644 --- a/components/x-increment/package.json +++ b/components/x-increment/package.json @@ -7,7 +7,6 @@ "module": "dist/Increment.esm.js", "browser": "dist/Increment.es5.js", "scripts": { - "prepare": "npm run build", "build": "node rollup.js", "start": "node rollup.js --watch" }, diff --git a/components/x-interaction/package.json b/components/x-interaction/package.json index 8bf33f778..53ea1709e 100644 --- a/components/x-interaction/package.json +++ b/components/x-interaction/package.json @@ -6,7 +6,6 @@ "module": "dist/Interaction.esm.js", "browser": "dist/Interaction.es5.js", "scripts": { - "prepare": "npm run build", "build": "node rollup.js", "start": "node rollup.js --watch" }, diff --git a/components/x-styling-demo/package.json b/components/x-styling-demo/package.json index bf0444556..22d48ce0e 100644 --- a/components/x-styling-demo/package.json +++ b/components/x-styling-demo/package.json @@ -8,7 +8,6 @@ "style": "dist/Button.css", "private": true, "scripts": { - "prepare": "npm run build", "build": "node rollup.js", "start": "node rollup.js --watch" }, diff --git a/components/x-teaser/package.json b/components/x-teaser/package.json index 5852abacb..b4289576e 100644 --- a/components/x-teaser/package.json +++ b/components/x-teaser/package.json @@ -7,7 +7,6 @@ "browser": "dist/Teaser.es5.js", "types": "Props.d.ts", "scripts": { - "prepare": "npm run build", "build": "node rollup.js", "start": "node rollup.js --watch" }, diff --git a/private/blueprints/component/package.json b/private/blueprints/component/package.json index 4dca01431..9c9d08241 100644 --- a/private/blueprints/component/package.json +++ b/private/blueprints/component/package.json @@ -6,7 +6,6 @@ "module": "dist/{{componentName}}.esm.js", "browser": "dist/{{componentName}}.es5.js", "scripts": { - "prepare": "npm run build", "build": "node rollup.js", "start": "node rollup.js --watch" }, From a968c911a81d3dbc911404adc6b4255dece4e3f3 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 17:51:43 +0100 Subject: [PATCH 058/760] buildpack order matters --- app.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.json b/app.json index c5d18fd4c..fff2056fb 100644 --- a/app.json +++ b/app.json @@ -7,7 +7,7 @@ } }, "buildpacks": [ - {"url": "https://github.com/heroku/heroku-buildpack-static.git"}, - {"url": "heroku/nodejs"} + {"url": "heroku/nodejs"}, + {"url": "https://github.com/heroku/heroku-buildpack-static.git"} ] } From a9f38c02c6225c888f09987e6bdd7d84fecca360 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 17:59:43 +0100 Subject: [PATCH 059/760] actually it did need that! huh! --- app.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app.json b/app.json index fff2056fb..e28d05fb9 100644 --- a/app.json +++ b/app.json @@ -1,5 +1,10 @@ { - "env": {}, + "env": { + "NPM_CONFIG_PRODUCTION": { + "description": "don't prune devDependencies", + "value": "false" + } + }, "formation": { "web": { "quantity": 1, From 7e28ddba49d853d948c77708e3cd3f4c5a445a70 Mon Sep 17 00:00:00 2001 From: bren Date: Wed, 8 May 2019 18:07:59 +0100 Subject: [PATCH 060/760] component builds are independent right let's do them in parallel --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12ce59ec3..99fdea400 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": true, "scripts": { "clean": "git clean -fxdi", - "build": "athloi run build && npm run build-storybook", + "build": "athloi run build --concurrency 3 && npm run build-storybook", "jest": "jest -c jest.config.js", "pretest": "npm run build", "test": "npm run lint && npm run jest", From ab898fbc3ef8984efa54f7846054c9ec9e2be8ae Mon Sep 17 00:00:00 2001 From: fenglish Date: Mon, 9 Jul 2018 11:34:25 +0100 Subject: [PATCH 061/760] =?UTF-8?q?create=20gift=20article=20component=20w?= =?UTF-8?q?ith=20npm=20start=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/.gitignore | 1 + components/x-gift-article/package.json | 24 +++++++++++++++++++ components/x-gift-article/rollup.config.js | 6 +++++ .../x-gift-article/src/XGiftArticle.jsx | 0 4 files changed, 31 insertions(+) create mode 100644 components/x-gift-article/.gitignore create mode 100644 components/x-gift-article/package.json create mode 100644 components/x-gift-article/rollup.config.js create mode 100644 components/x-gift-article/src/XGiftArticle.jsx diff --git a/components/x-gift-article/.gitignore b/components/x-gift-article/.gitignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/components/x-gift-article/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json new file mode 100644 index 000000000..74b4a7883 --- /dev/null +++ b/components/x-gift-article/package.json @@ -0,0 +1,24 @@ +{ + "name": "x-gift-article", + "version": "1.0.0", + "description": "This module provides templates for gift article form", + "main": "dist/XGiftArticle.cjs.js", + "scripts": { + "prepare": "npm run build", + "build": "rollup -c rollup.config.js", + "start": "rollup --watch -c rollup.config.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "browser": "dist/XGiftArticle.es5.js", + "module": "dist/XGiftArticle.esm.js", + "devDependencies": { + "@financial-times/x-engine": "^1.0.0-1", + "@financial-times/x-rollup": "^1.0.0", + "rollup": "^0.57.1" + }, + "peerDependencies": { + "@financial-times/x-engine": "^1.0.0-1" + } +} diff --git a/components/x-gift-article/rollup.config.js b/components/x-gift-article/rollup.config.js new file mode 100644 index 000000000..5dd290165 --- /dev/null +++ b/components/x-gift-article/rollup.config.js @@ -0,0 +1,6 @@ +import xRollup from '@financial-times/x-rollup'; +import pkg from './package.json'; + +const input = 'src/XGiftArticle.jsx'; + +export default xRollup({input, pkg}); diff --git a/components/x-gift-article/src/XGiftArticle.jsx b/components/x-gift-article/src/XGiftArticle.jsx new file mode 100644 index 000000000..e69de29bb From 3850856c2bafc5890abb70d168323e94e0cb23de Mon Sep 17 00:00:00 2001 From: fenglish Date: Mon, 9 Jul 2018 14:48:47 +0100 Subject: [PATCH 062/760] =?UTF-8?q?set=20up=20Storybook=20=20=F0=9F=90=BF?= =?UTF-8?q?=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/register-components.js | 1 + components/x-gift-article/package.json | 2 +- components/x-gift-article/src/XGiftArticle.jsx | 7 +++++++ components/x-gift-article/stories/index.js | 13 +++++++++++++ components/x-gift-article/stories/test.js | 9 +++++++++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 components/x-gift-article/stories/index.js create mode 100644 components/x-gift-article/stories/test.js diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 0d2bce45e..6fad0c357 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -2,6 +2,7 @@ const components = [ require('../components/x-teaser/stories'), require('../components/x-increment/stories'), require('../components/x-styling-demo/stories'), + require('../components/x-gift-article/stories'), ]; module.exports = components; diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 74b4a7883..7f7a1c871 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -1,5 +1,5 @@ { - "name": "x-gift-article", + "name": "@financial-times/x-gift-article", "version": "1.0.0", "description": "This module provides templates for gift article form", "main": "dist/XGiftArticle.cjs.js", diff --git a/components/x-gift-article/src/XGiftArticle.jsx b/components/x-gift-article/src/XGiftArticle.jsx index e69de29bb..3ca093c49 100644 --- a/components/x-gift-article/src/XGiftArticle.jsx +++ b/components/x-gift-article/src/XGiftArticle.jsx @@ -0,0 +1,7 @@ +import { h } from '@financial-times/x-engine'; + +const GiftArticle = ({name}) =>

    Hello, {name}!!

    ; + +export { + GiftArticle +}; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js new file mode 100644 index 000000000..77f5c966e --- /dev/null +++ b/components/x-gift-article/stories/index.js @@ -0,0 +1,13 @@ +const { GiftArticle } = require('../'); + +// if (typeof window !== 'undefined' && !/\.ft\.com$/.test(window.location.hostname)) { +// console.warn('Due to CORS restrictions some demos may not work outside of the ft.com domain'); +// } + +module.exports = { + name: 'x-gift-article', + component: GiftArticle, + stories: [ + require('./test') + ] +}; diff --git a/components/x-gift-article/stories/test.js b/components/x-gift-article/stories/test.js new file mode 100644 index 000000000..57bb1b831 --- /dev/null +++ b/components/x-gift-article/stories/test.js @@ -0,0 +1,9 @@ +exports.title = 'Test'; + +exports.data = { + name: 'ASUKA' +} + +// This reference is only required for hot module loading in development +// +exports.m = module; From c904d9c1d08a04bbfdf50da6f7dd8e81fdf19bc9 Mon Sep 17 00:00:00 2001 From: fenglish Date: Tue, 10 Jul 2018 17:38:52 +0100 Subject: [PATCH 063/760] =?UTF-8?q?prototype=20before=20changing=20class?= =?UTF-8?q?=20names=20to=20Origami=20classnames=20=20=F0=9F=90=BF=20v2.10.?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/package.json | 10 +- components/x-gift-article/rollup.config.js | 2 +- components/x-gift-article/src/Buttons.jsx | 22 ++++ components/x-gift-article/src/Form.jsx | 50 ++++++++ components/x-gift-article/src/GiftArticle.css | 121 ++++++++++++++++++ components/x-gift-article/src/GiftArticle.jsx | 19 +++ components/x-gift-article/src/Message.jsx | 17 +++ .../src/RadioButtonsSection.jsx | 21 +++ components/x-gift-article/src/Title.jsx | 5 + components/x-gift-article/src/Url.jsx | 19 +++ components/x-gift-article/src/UrlSection.jsx | 24 ++++ .../x-gift-article/src/XGiftArticle.jsx | 7 - components/x-gift-article/stories/index.js | 18 ++- components/x-gift-article/stories/knobs.js | 9 ++ components/x-gift-article/stories/test.js | 9 -- .../stories/with-gift-credits.js | 36 ++++++ 16 files changed, 358 insertions(+), 31 deletions(-) create mode 100644 components/x-gift-article/src/Buttons.jsx create mode 100644 components/x-gift-article/src/Form.jsx create mode 100644 components/x-gift-article/src/GiftArticle.css create mode 100644 components/x-gift-article/src/GiftArticle.jsx create mode 100644 components/x-gift-article/src/Message.jsx create mode 100644 components/x-gift-article/src/RadioButtonsSection.jsx create mode 100644 components/x-gift-article/src/Title.jsx create mode 100644 components/x-gift-article/src/Url.jsx create mode 100644 components/x-gift-article/src/UrlSection.jsx delete mode 100644 components/x-gift-article/src/XGiftArticle.jsx create mode 100644 components/x-gift-article/stories/knobs.js delete mode 100644 components/x-gift-article/stories/test.js create mode 100644 components/x-gift-article/stories/with-gift-credits.js diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 7f7a1c871..2b022df81 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -2,7 +2,10 @@ "name": "@financial-times/x-gift-article", "version": "1.0.0", "description": "This module provides templates for gift article form", - "main": "dist/XGiftArticle.cjs.js", + "main": "dist/GiftArticle.cjs.js", + "browser": "dist/GiftArticle.es5.js", + "module": "dist/GiftArticle.esm.js", + "style": "dist/GiftArticle.css", "scripts": { "prepare": "npm run build", "build": "rollup -c rollup.config.js", @@ -11,11 +14,10 @@ "keywords": [], "author": "", "license": "ISC", - "browser": "dist/XGiftArticle.es5.js", - "module": "dist/XGiftArticle.esm.js", - "devDependencies": { + "dependencies": { "@financial-times/x-engine": "^1.0.0-1", "@financial-times/x-rollup": "^1.0.0", + "@financial-times/x-interaction": "^1.0.0-1", "rollup": "^0.57.1" }, "peerDependencies": { diff --git a/components/x-gift-article/rollup.config.js b/components/x-gift-article/rollup.config.js index 5dd290165..c303f0745 100644 --- a/components/x-gift-article/rollup.config.js +++ b/components/x-gift-article/rollup.config.js @@ -1,6 +1,6 @@ import xRollup from '@financial-times/x-rollup'; import pkg from './package.json'; -const input = 'src/XGiftArticle.jsx'; +const input = 'src/GiftArticle.jsx'; export default xRollup({input, pkg}); diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx new file mode 100644 index 000000000..78ea26fcc --- /dev/null +++ b/components/x-gift-article/src/Buttons.jsx @@ -0,0 +1,22 @@ +import { h } from '@financial-times/x-engine'; + +export default ({ isGift, isGiftUrlCreated, mailtoUrl, createGiftUrl }) => { + + if (isGiftUrlCreated || !isGift) { + return ( +
    + + Email link +
    + ); + } + + return ( +
    + +
    + ); + +}; diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx new file mode 100644 index 000000000..cdf77bfea --- /dev/null +++ b/components/x-gift-article/src/Form.jsx @@ -0,0 +1,50 @@ +import { h } from '@financial-times/x-engine'; +import { withActions } from '@financial-times/x-interaction'; +import RadioButtonsSection from './RadioButtonsSection'; +import UrlSection from './UrlSection'; + +let isGiftUrlCreated = false; + +const withRadioButtonActions = withActions(({ url, giftUrl, nonGiftUrl, isGiftUrlCreated, mailtoGiftUrl, mailtoNonGiftUrl }) => ({ + displayGiftUrlSection() { + return { + isGift: true, + url: isGiftUrlCreated ? giftUrl : url, + mailtoUrl: mailtoGiftUrl + } + }, + displayNonGiftUrlSection() { + return { + isGift: false, + url: nonGiftUrl, + mailtoUrl: mailtoNonGiftUrl + } + }, + createGiftUrl() { + isGiftUrlCreated = true; + return { + isGiftUrlCreated: true, + url: giftUrl, + mailtoUrl: mailtoGiftUrl + } + } +})); + +const BaseTemplate = (data) => ( +
    + + +
    +); + +const Form = withRadioButtonActions(BaseTemplate); + +export { Form }; diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css new file mode 100644 index 000000000..dcf0c8bd0 --- /dev/null +++ b/components/x-gift-article/src/GiftArticle.css @@ -0,0 +1,121 @@ +strong { + font-weight: 600; +} + +.gift-form { + width: 500px; +} + +.gift-form__group { + border: 0; + padding: 0; +} + +.gift-form__button { + display: inline-block; + box-sizing: border-box; + vertical-align: middle; + margin: 0; + border-style: solid; + text-align: center; + text-decoration: none; + font-family: MetricWeb,sans-serif; + font-weight: 400; + border-radius: 0; + cursor: pointer; + transition: 0.3s background-color, 0.15s color ease-out, 0.15s border-color ease-out; + user-select: none; + background-clip: border-box; + padding: 6px 8px 6px 8px; + background-color: #0d7680; + color: #fff; + border-color: rgba(0,0,0,0); + background-size: 40px 40px; + min-height: 40px; + min-width: 80px; + padding: 11px 20px 11px 20px; + font-size: 16px; + line-height: 16px; + border-width: 1px; + margin-right: 5px; +} + +.gift-form__create-link, +.gift-form__show-link { + display: grid; + grid-template-columns: auto auto; + grid-template-rows: auto auto; + grid-template-areas: "share-input share-buttons" + "message message"; + grid-column-gap: 20px; +} + +.gift-form__link-input { + grid-area: share-input; + margin-bottom: 8px; +} + +.gift-form__buttons { + grid-area: share-buttons; +} + +.gift-form__message { + grid-area: message; + font-family: MetricWeb,sans-serif; + font-size: 16px; + line-height: 20px; +} + +.gift-form__title { + font-family: MetricWeb, sans-serif; + font-size: 20px; + line-height: 24px; + font-weight: 600; + margin-bottom: 24px; +} + +.gift-form__label { + font-family: MetricWeb,sans-serif; + display: inline-block; + margin-left: 4px; + font-size: 16px; + line-height: 20px; +} + +.gift-form__text-input { + font-size: 16px; + line-height: 20px; + color: #33302e; + border-color: #b3a9a0; + background-color: #fff; + margin-top: 12px; + box-sizing: border-box; + width: 100%; + min-height: 40px; + padding: 9px 9px 9px; + border: 1px solid #b3a9a0; + border-radius: 0; + background-clip: padding-box; + font-family: MetricWeb,sans-serif; + outline: none; + transition: 0.15s -webkit-box-shadow ease-in; + transition: 0.15s box-shadow ease-in; + transition: 0.15s box-shadow ease-in, 0.15s -webkit-box-shadow ease-in; + appearance: none; + max-width: none; + margin: 0; +} + +.gift-form__text-input:disabled { + color: #66605c; + border-color: #e6d9ce; + background-color: #e6d9ce; + cursor: default; +} + +.gift-form__radio-input { + display: inline-block; + margin-right: 20px; + margin-bottom: 8px; + white-space: nowrap; +} diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx new file mode 100644 index 000000000..cc2982999 --- /dev/null +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -0,0 +1,19 @@ +import { h } from '@financial-times/x-engine'; +import Title from './Title'; +import { Form } from './Form'; + +const GiftArticle = (data) => { + + return ( +
    + + <fieldset className="gift-form__group"> + <Form {...data}/> + </fieldset> + </form> + ); +} + +export { + GiftArticle +}; diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx new file mode 100644 index 000000000..a53abf27f --- /dev/null +++ b/components/x-gift-article/src/Message.jsx @@ -0,0 +1,17 @@ +import { h } from '@financial-times/x-engine'; + +export default ({ isGift, isGiftUrlCreated, credit }) => { + + const messageSubscriber = 'This link can only be read by existing subscribers'; + const messageCopyLimit = 'This link can be opened up to 3 times'; + const messageGiftCreditRemaining = [ 'You have ', <strong>{ credit } gift articles</strong>, ' left this month' ]; + + let message = messageSubscriber; + + if (isGift) { + message = isGiftUrlCreated ? messageCopyLimit : messageGiftCreditRemaining; + } + + return (<div className="gift-form__message">{ message }</div>); + +}; diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx new file mode 100644 index 000000000..e6d3c2c77 --- /dev/null +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -0,0 +1,21 @@ +import { h } from '@financial-times/x-engine'; + +export default ({ displayGiftUrlSection, displayNonGiftUrlSection }) => ( + <div className="gift-form__radio-group"> + + <div className="gift-form__radio-input"> + <input type="radio" name="gift-form__radio" value="giftLink" className="gift-form__radio" id="giftLink" defaultChecked onChange={ displayGiftUrlSection }></input> + <label htmlFor="giftLink" className="gift-form__label"> + <div className="gift-form__label">with <strong>anyone</strong></div> + </label> + </div> + + <div className="gift-form__radio-input"> + <input type="radio" name="gift-form__radio" value="nonGiftLink" className="gift-form__radio" id="nonGiftLink" onChange={ displayNonGiftUrlSection }></input> + <label htmlFor="nonGiftLink" className="gift-form__label"> + <div className="gift-form__label">with <strong>other subscribers</strong></div> + </label> + </div> + + </div> +); diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx new file mode 100644 index 000000000..a1b09d0e3 --- /dev/null +++ b/components/x-gift-article/src/Title.jsx @@ -0,0 +1,5 @@ +import { h } from '@financial-times/x-engine'; + +export default ({ title }) => ( + <div className="gift-form__title">{ title }</div> +); diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx new file mode 100644 index 000000000..84e3546a3 --- /dev/null +++ b/components/x-gift-article/src/Url.jsx @@ -0,0 +1,19 @@ +import { h } from '@financial-times/x-engine'; + +export default ({ isGift, isGiftUrlCreated, url }) => { + + if (!isGift || isGiftUrlCreated) { + return ( + <div className="gift-form__link-input"> + <input type="text" name="example-gift-link" value={ url } className="gift-form__text-input"></input> + </div> + ); + } + + return ( + <div className="gift-form__link-input"> + <input type="text" name="example-gift-link" value={ url } className="gift-form__text-input" disabled='disabled'></input> + </div> + ); + +}; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx new file mode 100644 index 000000000..bb3e6c68b --- /dev/null +++ b/components/x-gift-article/src/UrlSection.jsx @@ -0,0 +1,24 @@ +import { h } from '@financial-times/x-engine'; +import Url from './Url'; +import Message from './Message'; +import Buttons from './Buttons'; + +export default ({ isGift, isGiftUrlCreated, url, credit, mailtoUrl, createGiftUrl }) => ( + <div className="gift-form__radio-section" data-section-id="giftLink" data-trackable="giftLink"> + <div className="gift-form__create-link"> + <Url + isGift={ isGift } + isGiftUrlCreated={ isGiftUrlCreated } + url={ url }/> + <Message + isGift={ isGift } + isGiftUrlCreated={ isGiftUrlCreated } + credit={ credit }/> + <Buttons + isGift={ isGift } + isGiftUrlCreated={ isGiftUrlCreated } + mailtoUrl={ mailtoUrl } + createGiftUrl={ createGiftUrl }/> + </div> + </div> +); diff --git a/components/x-gift-article/src/XGiftArticle.jsx b/components/x-gift-article/src/XGiftArticle.jsx deleted file mode 100644 index 3ca093c49..000000000 --- a/components/x-gift-article/src/XGiftArticle.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { h } from '@financial-times/x-engine'; - -const GiftArticle = ({name}) => <h1>Hello, {name}!!</h1>; - -export { - GiftArticle -}; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 77f5c966e..4aed78215 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -1,13 +1,11 @@ const { GiftArticle } = require('../'); -// if (typeof window !== 'undefined' && !/\.ft\.com$/.test(window.location.hostname)) { -// console.warn('Due to CORS restrictions some demos may not work outside of the ft.com domain'); -// } - -module.exports = { - name: 'x-gift-article', - component: GiftArticle, - stories: [ - require('./test') - ] +exports.component = GiftArticle; +exports.package = require('../package.json'); +exports.dependencies = { + 'o-fonts': '^3.0.0', }; +exports.stories = [ + require('./with-gift-credits') +]; +exports.knobs = require('./knobs'); diff --git a/components/x-gift-article/stories/knobs.js b/components/x-gift-article/stories/knobs.js new file mode 100644 index 000000000..6f9ef7dac --- /dev/null +++ b/components/x-gift-article/stories/knobs.js @@ -0,0 +1,9 @@ +// To ensure that component stories do not need to depend on Storybook themselves we return a +// function that may be passed the required dependencies. +module.exports = (data, { object, text, number, boolean, date, selectV2 }) => { + return { + title() { + return text('Title', data.title); + } + } +}; diff --git a/components/x-gift-article/stories/test.js b/components/x-gift-article/stories/test.js deleted file mode 100644 index 57bb1b831..000000000 --- a/components/x-gift-article/stories/test.js +++ /dev/null @@ -1,9 +0,0 @@ -exports.title = 'Test'; - -exports.data = { - name: 'ASUKA' -} - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js new file mode 100644 index 000000000..a5cf797fb --- /dev/null +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -0,0 +1,36 @@ +exports.title = 'With gift credits'; + +exports.data = { + title: 'Share this article', + isFreeArticle: false, + credit: 20, + monthlyAllowance: 20, + dateText: 'May 1', + isGift: true, + url: 'dummy-url', + giftUrl: 'gift-url', + nonGiftUrl: 'non-gift-url', + mailtoUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', + mailtoGiftUrl: 'mailto:?subject=title&body=giftMailtoUrl', + mailtoNonGiftUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', + isGiftUrlCreated: false +}; + +exports.knobs = [ + 'showTitle', + 'title', + 'isFreeArticle', + 'credit', + 'isGift', + 'giftUrl', + 'nonGiftUrl', + 'url', + 'mailtoUrl', + 'mailtoGiftUrl', + 'mailtoNonGiftUrl', + 'isGiftUrlCreated' +]; + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module; From 1962794b792df1cfadb68fe00b0a1b419ec966b4 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 16 Jul 2018 16:32:14 +0100 Subject: [PATCH 064/760] =?UTF-8?q?apply=20o-buttons=20class=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Buttons.jsx | 6 +++--- components/x-gift-article/stories/index.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 78ea26fcc..e57805297 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -5,15 +5,15 @@ export default ({ isGift, isGiftUrlCreated, mailtoUrl, createGiftUrl }) => { if (isGiftUrlCreated || !isGift) { return ( <div className="gift-form__buttons"> - <button className="gift-form__button js-copy-link" type="button">Copy link</button> - <a className="gift-form__button js-email-link" href={ mailtoUrl } target="_blank">Email link</a> + <button className="o-buttons o-buttons--primary o-buttons--big js-copy-link" type="button">Copy link</button> + <a className="o-buttons o-buttons--primary o-buttons--big" href={ mailtoUrl } target="_blank">Email link</a> </div> ); } return ( <div className="gift-form__buttons"> - <button className="gift-form__button js-create-gift-link" type="button" onClick={ createGiftUrl }> + <button className="o-buttons o-buttons--primary o-buttons--big" type="button" onClick={ createGiftUrl }> Create gift link </button> </div> diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 4aed78215..86b271ec4 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -4,6 +4,7 @@ exports.component = GiftArticle; exports.package = require('../package.json'); exports.dependencies = { 'o-fonts': '^3.0.0', + 'o-buttons': '^5.13.1' }; exports.stories = [ require('./with-gift-credits') From 0e463d989c6a7b2c6edcc7f4ca5c666b59188209 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 16 Jul 2018 17:34:02 +0100 Subject: [PATCH 065/760] =?UTF-8?q?apply=20o-form=20class=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Buttons.jsx | 10 +- components/x-gift-article/src/Form.jsx | 5 + components/x-gift-article/src/GiftArticle.css | 121 ------------------ components/x-gift-article/src/GiftArticle.jsx | 4 +- .../src/RadioButtonsSection.jsx | 37 ++++-- components/x-gift-article/src/Title.jsx | 2 +- components/x-gift-article/src/Url.jsx | 12 +- components/x-gift-article/src/UrlSection.jsx | 9 +- components/x-gift-article/stories/index.js | 4 +- 9 files changed, 45 insertions(+), 159 deletions(-) delete mode 100644 components/x-gift-article/src/GiftArticle.css diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index e57805297..71bdfd3b9 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -4,15 +4,17 @@ export default ({ isGift, isGiftUrlCreated, mailtoUrl, createGiftUrl }) => { if (isGiftUrlCreated || !isGift) { return ( - <div className="gift-form__buttons"> - <button className="o-buttons o-buttons--primary o-buttons--big js-copy-link" type="button">Copy link</button> - <a className="o-buttons o-buttons--primary o-buttons--big" href={ mailtoUrl } target="_blank">Email link</a> + <div className="o-forms__suffix"> + <div className="gift-form__buttons-align"> + <button className="o-buttons o-buttons--primary o-buttons--big gift-form__button--with-gap js-copy-link" type="button">Copy link</button> + <a className="o-buttons o-buttons--primary o-buttons--big" href={ mailtoUrl } target="_blank">Email link</a> + </div> </div> ); } return ( - <div className="gift-form__buttons"> + <div className="o-forms__suffix"> <button className="o-buttons o-buttons--primary o-buttons--big" type="button" onClick={ createGiftUrl }> Create gift link </button> diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index cdf77bfea..530853f2d 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -2,6 +2,7 @@ import { h } from '@financial-times/x-engine'; import { withActions } from '@financial-times/x-interaction'; import RadioButtonsSection from './RadioButtonsSection'; import UrlSection from './UrlSection'; +import Message from './Message'; let isGiftUrlCreated = false; @@ -42,6 +43,10 @@ const BaseTemplate = (data) => ( createGiftUrl={ data.actions.createGiftUrl } credit={ data.credit } url={ data.url }/> + <Message + isGift={ data.isGift } + isGiftUrlCreated={ data.isGiftUrlCreated } + credit={ data.credit }/> </div> ); diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css deleted file mode 100644 index dcf0c8bd0..000000000 --- a/components/x-gift-article/src/GiftArticle.css +++ /dev/null @@ -1,121 +0,0 @@ -strong { - font-weight: 600; -} - -.gift-form { - width: 500px; -} - -.gift-form__group { - border: 0; - padding: 0; -} - -.gift-form__button { - display: inline-block; - box-sizing: border-box; - vertical-align: middle; - margin: 0; - border-style: solid; - text-align: center; - text-decoration: none; - font-family: MetricWeb,sans-serif; - font-weight: 400; - border-radius: 0; - cursor: pointer; - transition: 0.3s background-color, 0.15s color ease-out, 0.15s border-color ease-out; - user-select: none; - background-clip: border-box; - padding: 6px 8px 6px 8px; - background-color: #0d7680; - color: #fff; - border-color: rgba(0,0,0,0); - background-size: 40px 40px; - min-height: 40px; - min-width: 80px; - padding: 11px 20px 11px 20px; - font-size: 16px; - line-height: 16px; - border-width: 1px; - margin-right: 5px; -} - -.gift-form__create-link, -.gift-form__show-link { - display: grid; - grid-template-columns: auto auto; - grid-template-rows: auto auto; - grid-template-areas: "share-input share-buttons" - "message message"; - grid-column-gap: 20px; -} - -.gift-form__link-input { - grid-area: share-input; - margin-bottom: 8px; -} - -.gift-form__buttons { - grid-area: share-buttons; -} - -.gift-form__message { - grid-area: message; - font-family: MetricWeb,sans-serif; - font-size: 16px; - line-height: 20px; -} - -.gift-form__title { - font-family: MetricWeb, sans-serif; - font-size: 20px; - line-height: 24px; - font-weight: 600; - margin-bottom: 24px; -} - -.gift-form__label { - font-family: MetricWeb,sans-serif; - display: inline-block; - margin-left: 4px; - font-size: 16px; - line-height: 20px; -} - -.gift-form__text-input { - font-size: 16px; - line-height: 20px; - color: #33302e; - border-color: #b3a9a0; - background-color: #fff; - margin-top: 12px; - box-sizing: border-box; - width: 100%; - min-height: 40px; - padding: 9px 9px 9px; - border: 1px solid #b3a9a0; - border-radius: 0; - background-clip: padding-box; - font-family: MetricWeb,sans-serif; - outline: none; - transition: 0.15s -webkit-box-shadow ease-in; - transition: 0.15s box-shadow ease-in; - transition: 0.15s box-shadow ease-in, 0.15s -webkit-box-shadow ease-in; - appearance: none; - max-width: none; - margin: 0; -} - -.gift-form__text-input:disabled { - color: #66605c; - border-color: #e6d9ce; - background-color: #e6d9ce; - cursor: default; -} - -.gift-form__radio-input { - display: inline-block; - margin-right: 20px; - margin-bottom: 8px; - white-space: nowrap; -} diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index cc2982999..d2293cfdd 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -6,8 +6,8 @@ const GiftArticle = (data) => { return ( <form name="gift-form" className="gift-form"> - <Title title={ data.title }/> - <fieldset className="gift-form__group"> + <fieldset className="o-forms"> + <Title title={ data.title }/> <Form {...data}/> </fieldset> </form> diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index e6d3c2c77..59ff74a5c 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -1,21 +1,32 @@ import { h } from '@financial-times/x-engine'; export default ({ displayGiftUrlSection, displayNonGiftUrlSection }) => ( - <div className="gift-form__radio-group"> + <div class="o-forms__group o-forms__group--inline"> - <div className="gift-form__radio-input"> - <input type="radio" name="gift-form__radio" value="giftLink" className="gift-form__radio" id="giftLink" defaultChecked onChange={ displayGiftUrlSection }></input> - <label htmlFor="giftLink" className="gift-form__label"> - <div className="gift-form__label">with <strong>anyone</strong></div> - </label> - </div> + <input type="radio" name="gift-form__radio" value="giftLink" class="o-forms__radio" id="giftLink" defaultChecked onChange={ displayGiftUrlSection }/> + <label htmlFor="giftLink" class="o-forms__label">with <strong>anyone</strong></label> - <div className="gift-form__radio-input"> - <input type="radio" name="gift-form__radio" value="nonGiftLink" className="gift-form__radio" id="nonGiftLink" onChange={ displayNonGiftUrlSection }></input> - <label htmlFor="nonGiftLink" className="gift-form__label"> - <div className="gift-form__label">with <strong>other subscribers</strong></div> - </label> - </div> + <input type="radio" name="gift-form__radio" value="nonGiftLink" class="o-forms__radio" id="nonGiftLink" onChange={ displayNonGiftUrlSection }/> + <label htmlFor="nonGiftLink" class="o-forms__label">with <strong>other subscribers</strong></label> </div> ); + + +// <div className="o-forms__group o-forms__group--inline"> +// +// <div className="gift-form__radio-input"> +// <input type="radio" name="gift-form__radio" value="giftLink" className="o-forms__radio" id="giftLink" defaultChecked ></input> +// <label htmlFor="giftLink" className="gift-form__label"> +// <div className="o-forms__label"></div> +// </label> +// </div> +// +// <div className="gift-form__radio-input"> +// <input type="radio" name="gift-form__radio" value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" ></input> +// <label htmlFor="nonGiftLink" className="gift-form__label"> +// <div className="o-forms__label"></div> +// </label> +// </div> +// +// </div> diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx index a1b09d0e3..731701f07 100644 --- a/components/x-gift-article/src/Title.jsx +++ b/components/x-gift-article/src/Title.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; export default ({ title }) => ( - <div className="gift-form__title">{ title }</div> + <div className="o-typography-product-heading-level-7">{ title }</div> ); diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 84e3546a3..55f08dd49 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -3,17 +3,9 @@ import { h } from '@financial-times/x-engine'; export default ({ isGift, isGiftUrlCreated, url }) => { if (!isGift || isGiftUrlCreated) { - return ( - <div className="gift-form__link-input"> - <input type="text" name="example-gift-link" value={ url } className="gift-form__text-input"></input> - </div> - ); + return (<input type="text" name="example-gift-link" value={ url } className="o-forms__text"></input>); } - return ( - <div className="gift-form__link-input"> - <input type="text" name="example-gift-link" value={ url } className="gift-form__text-input" disabled='disabled'></input> - </div> - ); + return (<input type="text" name="example-gift-link" value={ url } className="o-forms__text" disabled='disabled'></input>); }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index bb3e6c68b..20a59ee52 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -1,19 +1,14 @@ import { h } from '@financial-times/x-engine'; import Url from './Url'; -import Message from './Message'; import Buttons from './Buttons'; -export default ({ isGift, isGiftUrlCreated, url, credit, mailtoUrl, createGiftUrl }) => ( +export default ({ isGift, isGiftUrlCreated, url, mailtoUrl, createGiftUrl }) => ( <div className="gift-form__radio-section" data-section-id="giftLink" data-trackable="giftLink"> - <div className="gift-form__create-link"> + <div className="gift-form__create-link o-forms__affix-wrapper"> <Url isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } url={ url }/> - <Message - isGift={ isGift } - isGiftUrlCreated={ isGiftUrlCreated } - credit={ credit }/> <Buttons isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 86b271ec4..bcf415229 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -4,7 +4,9 @@ exports.component = GiftArticle; exports.package = require('../package.json'); exports.dependencies = { 'o-fonts': '^3.0.0', - 'o-buttons': '^5.13.1' + 'o-buttons': '^5.13.1', + 'o-forms': '^5.7.3', + 'o-typography': '^5.7.5' }; exports.stories = [ require('./with-gift-credits') From 974e5b2020b9e19fcb6b8ac8dd7778ad6caf7280 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 16 Jul 2018 17:55:54 +0100 Subject: [PATCH 066/760] =?UTF-8?q?set=20correct=20tracking=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Form.jsx | 9 ++++++--- components/x-gift-article/src/UrlSection.jsx | 4 ++-- components/x-gift-article/stories/with-gift-credits.js | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 530853f2d..b3140f698 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -11,14 +11,16 @@ const withRadioButtonActions = withActions(({ url, giftUrl, nonGiftUrl, isGiftUr return { isGift: true, url: isGiftUrlCreated ? giftUrl : url, - mailtoUrl: mailtoGiftUrl + mailtoUrl: mailtoGiftUrl, + tracking: 'giftLink' } }, displayNonGiftUrlSection() { return { isGift: false, url: nonGiftUrl, - mailtoUrl: mailtoNonGiftUrl + mailtoUrl: mailtoNonGiftUrl, + tracking: 'nonGiftLink' } }, createGiftUrl() { @@ -42,7 +44,8 @@ const BaseTemplate = (data) => ( mailtoUrl={ data.mailtoUrl } createGiftUrl={ data.actions.createGiftUrl } credit={ data.credit } - url={ data.url }/> + url={ data.url } + tracking={ data.tracking }/> <Message isGift={ data.isGift } isGiftUrlCreated={ data.isGiftUrlCreated } diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 20a59ee52..e0d943b31 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -2,8 +2,8 @@ import { h } from '@financial-times/x-engine'; import Url from './Url'; import Buttons from './Buttons'; -export default ({ isGift, isGiftUrlCreated, url, mailtoUrl, createGiftUrl }) => ( - <div className="gift-form__radio-section" data-section-id="giftLink" data-trackable="giftLink"> +export default ({ isGift, isGiftUrlCreated, url, mailtoUrl, createGiftUrl, tracking }) => ( + <div className="gift-form__radio-section" data-section-id="giftLink" data-trackable={ tracking }> <div className="gift-form__create-link o-forms__affix-wrapper"> <Url isGift={ isGift } diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index a5cf797fb..eac2e7383 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -13,7 +13,8 @@ exports.data = { mailtoUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', mailtoGiftUrl: 'mailto:?subject=title&body=giftMailtoUrl', mailtoNonGiftUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', - isGiftUrlCreated: false + isGiftUrlCreated: false, + tracking: 'giftLink' }; exports.knobs = [ @@ -28,7 +29,8 @@ exports.knobs = [ 'mailtoUrl', 'mailtoGiftUrl', 'mailtoNonGiftUrl', - 'isGiftUrlCreated' + 'isGiftUrlCreated', + 'tracking' ]; // This reference is only required for hot module loading in development From 49ba26f800a62d33580c98b61248ae0405ad6aaa Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 17 Jul 2018 16:16:24 +0100 Subject: [PATCH 067/760] =?UTF-8?q?add=20stylings=20=20=F0=9F=90=BF=20v2.1?= =?UTF-8?q?0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Buttons.jsx | 28 ++++++-- components/x-gift-article/src/Form.jsx | 58 ---------------- components/x-gift-article/src/GiftArticle.css | 49 ++++++++++++++ components/x-gift-article/src/GiftArticle.jsx | 67 +++++++++++++++---- components/x-gift-article/src/Message.jsx | 5 +- .../src/RadioButtonsSection.jsx | 37 ++++------ components/x-gift-article/src/Title.jsx | 8 ++- components/x-gift-article/src/Url.jsx | 10 ++- components/x-gift-article/src/UrlSection.jsx | 30 +++++---- 9 files changed, 171 insertions(+), 121 deletions(-) delete mode 100644 components/x-gift-article/src/Form.jsx create mode 100644 components/x-gift-article/src/GiftArticle.css diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 71bdfd3b9..a0590ae71 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -1,21 +1,35 @@ import { h } from '@financial-times/x-engine'; +import styles from './GiftArticle.css'; + +const ButtonsClassName = styles.buttons; + +const ButtonClassNames = [ + 'o-buttons', + 'o-buttons--primary', + 'o-buttons--big' +].join(' '); + +const ButtonWithGapClassNames = [ + ButtonClassNames, + 'js-copy-link', + styles['button--with-gap'] +].join(' '); + export default ({ isGift, isGiftUrlCreated, mailtoUrl, createGiftUrl }) => { if (isGiftUrlCreated || !isGift) { return ( - <div className="o-forms__suffix"> - <div className="gift-form__buttons-align"> - <button className="o-buttons o-buttons--primary o-buttons--big gift-form__button--with-gap js-copy-link" type="button">Copy link</button> - <a className="o-buttons o-buttons--primary o-buttons--big" href={ mailtoUrl } target="_blank">Email link</a> - </div> + <div className={ ButtonsClassName }> + <button className={ ButtonWithGapClassNames } type="button">Copy link</button> + <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank">Email link</a> </div> ); } return ( - <div className="o-forms__suffix"> - <button className="o-buttons o-buttons--primary o-buttons--big" type="button" onClick={ createGiftUrl }> + <div className={ ButtonsClassName }> + <button className={ ButtonClassNames } type="button" onClick={ createGiftUrl }> Create gift link </button> </div> diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx deleted file mode 100644 index b3140f698..000000000 --- a/components/x-gift-article/src/Form.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import { h } from '@financial-times/x-engine'; -import { withActions } from '@financial-times/x-interaction'; -import RadioButtonsSection from './RadioButtonsSection'; -import UrlSection from './UrlSection'; -import Message from './Message'; - -let isGiftUrlCreated = false; - -const withRadioButtonActions = withActions(({ url, giftUrl, nonGiftUrl, isGiftUrlCreated, mailtoGiftUrl, mailtoNonGiftUrl }) => ({ - displayGiftUrlSection() { - return { - isGift: true, - url: isGiftUrlCreated ? giftUrl : url, - mailtoUrl: mailtoGiftUrl, - tracking: 'giftLink' - } - }, - displayNonGiftUrlSection() { - return { - isGift: false, - url: nonGiftUrl, - mailtoUrl: mailtoNonGiftUrl, - tracking: 'nonGiftLink' - } - }, - createGiftUrl() { - isGiftUrlCreated = true; - return { - isGiftUrlCreated: true, - url: giftUrl, - mailtoUrl: mailtoGiftUrl - } - } -})); - -const BaseTemplate = (data) => ( - <div className="gift-form__form"> - <RadioButtonsSection - displayGiftUrlSection={ data.actions.displayGiftUrlSection } - displayNonGiftUrlSection={ data.actions.displayNonGiftUrlSection }/> - <UrlSection - isGift={ data.isGift } - isGiftUrlCreated={ data.isGiftUrlCreated } - mailtoUrl={ data.mailtoUrl } - createGiftUrl={ data.actions.createGiftUrl } - credit={ data.credit } - url={ data.url } - tracking={ data.tracking }/> - <Message - isGift={ data.isGift } - isGiftUrlCreated={ data.isGiftUrlCreated } - credit={ data.credit }/> - </div> -); - -const Form = withRadioButtonActions(BaseTemplate); - -export { Form }; diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css new file mode 100644 index 000000000..84ae55f61 --- /dev/null +++ b/components/x-gift-article/src/GiftArticle.css @@ -0,0 +1,49 @@ +.container { + font-family: MetricWeb,sans-serif; +} + +.bold { + font-weight: 600; +} + +.radio-button-section { + margin-bottom: 12px; +} + +@media only screen and (min-width: 600px) { + .url-section { + display: grid; + grid-template-columns: auto min-content; + grid-template-rows: auto auto; + grid-template-areas: "share-url buttons" + "message message"; + grid-column-gap: 20px; + } +} + +.title { + font-size: 20px; + line-height: 24px; + margin-bottom: 24px; +} + +.url { + grid-area: share-url; +} + +.message { + grid-area: message; + font-size: 16px; + margin-top: 12px; +} + +.buttons { + grid-area: buttons; + text-align: right; + white-space: nowrap; + margin-top: 12px; +} + +.button--with-gap { + margin-right: 5px; +} diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index d2293cfdd..eb32ba03a 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -1,19 +1,58 @@ import { h } from '@financial-times/x-engine'; +import { withActions } from '@financial-times/x-interaction'; import Title from './Title'; -import { Form } from './Form'; +import RadioButtonsSection from './RadioButtonsSection'; +import UrlSection from './UrlSection'; +import styles from './GiftArticle.css'; -const GiftArticle = (data) => { +let isGiftUrlCreated = false; - return ( - <form name="gift-form" className="gift-form"> - <fieldset className="o-forms"> - <Title title={ data.title }/> - <Form {...data}/> - </fieldset> - </form> - ); -} +const withRadioButtonActions = withActions(({ url, giftUrl, nonGiftUrl, isGiftUrlCreated, mailtoGiftUrl, mailtoNonGiftUrl }) => ({ + displayGiftUrlSection() { + return { + isGift: true, + url: isGiftUrlCreated ? giftUrl : url, + mailtoUrl: mailtoGiftUrl, + tracking: 'giftLink' + } + }, + displayNonGiftUrlSection() { + return { + isGift: false, + url: nonGiftUrl, + mailtoUrl: mailtoNonGiftUrl, + tracking: 'nonGiftLink' + } + }, + createGiftUrl() { + isGiftUrlCreated = true; + return { + isGiftUrlCreated: true, + url: giftUrl, + mailtoUrl: mailtoGiftUrl + } + } +})); -export { - GiftArticle -}; +const BaseTemplate = (data) => ( + <form name="gift-form" className={ styles.container }> + <fieldset className="o-forms"> + <Title title={ data.title }/> + <RadioButtonsSection + displayGiftUrlSection={ data.actions.displayGiftUrlSection } + displayNonGiftUrlSection={ data.actions.displayNonGiftUrlSection }/> + <UrlSection + tracking={ data.tracking } + isGift={ data.isGift } + isGiftUrlCreated={ data.isGiftUrlCreated } + url={ data.url } + credit={ data.credit } + mailtoUrl={ data.mailtoUrl } + createGiftUrl={ data.actions.createGiftUrl }/> + </fieldset> + </form> +); + +const GiftArticle = withRadioButtonActions(BaseTemplate); + +export { GiftArticle }; diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index a53abf27f..306314cf1 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -1,10 +1,11 @@ import { h } from '@financial-times/x-engine'; +import styles from './GiftArticle.css'; export default ({ isGift, isGiftUrlCreated, credit }) => { const messageSubscriber = 'This link can only be read by existing subscribers'; const messageCopyLimit = 'This link can be opened up to 3 times'; - const messageGiftCreditRemaining = [ 'You have ', <strong>{ credit } gift articles</strong>, ' left this month' ]; + const messageGiftCreditRemaining = [ 'You have ', <span className={ styles.bold }>{ credit } gift articles</span>, ' left this month' ]; let message = messageSubscriber; @@ -12,6 +13,6 @@ export default ({ isGift, isGiftUrlCreated, credit }) => { message = isGiftUrlCreated ? messageCopyLimit : messageGiftCreditRemaining; } - return (<div className="gift-form__message">{ message }</div>); + return (<div className={ styles.message }>{ message }</div>); }; diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 59ff74a5c..eb28e6c37 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -1,32 +1,21 @@ import { h } from '@financial-times/x-engine'; +import styles from './GiftArticle.css'; + +const boldTextClassName = styles.bold; +const radioSectionClassNames = [ + 'o-forms__group', + 'o-forms__group--inline', + styles['radio-button-section'] +].join(' '); export default ({ displayGiftUrlSection, displayNonGiftUrlSection }) => ( - <div class="o-forms__group o-forms__group--inline"> + <div className={ radioSectionClassNames }> - <input type="radio" name="gift-form__radio" value="giftLink" class="o-forms__radio" id="giftLink" defaultChecked onChange={ displayGiftUrlSection }/> - <label htmlFor="giftLink" class="o-forms__label">with <strong>anyone</strong></label> + <input type="radio" name="gift-form__radio" value="giftLink" className="o-forms__radio" id="giftLink" defaultChecked onChange={ displayGiftUrlSection }/> + <label htmlFor="giftLink" className="o-forms__label">with <span className={ boldTextClassName }>anyone</span></label> - <input type="radio" name="gift-form__radio" value="nonGiftLink" class="o-forms__radio" id="nonGiftLink" onChange={ displayNonGiftUrlSection }/> - <label htmlFor="nonGiftLink" class="o-forms__label">with <strong>other subscribers</strong></label> + <input type="radio" name="gift-form__radio" value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" onChange={ displayNonGiftUrlSection }/> + <label htmlFor="nonGiftLink" className="o-forms__label">with <span className={ boldTextClassName }>other subscribers</span></label> </div> ); - - -// <div className="o-forms__group o-forms__group--inline"> -// -// <div className="gift-form__radio-input"> -// <input type="radio" name="gift-form__radio" value="giftLink" className="o-forms__radio" id="giftLink" defaultChecked ></input> -// <label htmlFor="giftLink" className="gift-form__label"> -// <div className="o-forms__label"></div> -// </label> -// </div> -// -// <div className="gift-form__radio-input"> -// <input type="radio" name="gift-form__radio" value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" ></input> -// <label htmlFor="nonGiftLink" className="gift-form__label"> -// <div className="o-forms__label"></div> -// </label> -// </div> -// -// </div> diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx index 731701f07..1b21cb583 100644 --- a/components/x-gift-article/src/Title.jsx +++ b/components/x-gift-article/src/Title.jsx @@ -1,5 +1,11 @@ import { h } from '@financial-times/x-engine'; +import styles from './GiftArticle.css'; + +const titleClassNames = [ + styles.title, + styles.bold +].join(' '); export default ({ title }) => ( - <div className="o-typography-product-heading-level-7">{ title }</div> + <div className={ titleClassNames }>{ title }</div> ); diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 55f08dd49..19283cbe6 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -1,11 +1,17 @@ import { h } from '@financial-times/x-engine'; +import styles from './GiftArticle.css'; + +const urlClassNames = [ + 'o-forms__text', + styles.url +].join(' '); export default ({ isGift, isGiftUrlCreated, url }) => { if (!isGift || isGiftUrlCreated) { - return (<input type="text" name="example-gift-link" value={ url } className="o-forms__text"></input>); + return (<input type="text" name="example-gift-link" value={ url } className={ urlClassNames }></input>); } - return (<input type="text" name="example-gift-link" value={ url } className="o-forms__text" disabled='disabled'></input>); + return (<input type="text" name="example-gift-link" value={ url } className={ urlClassNames } disabled='disabled'></input>); }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index e0d943b31..b43a41b55 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -1,19 +1,23 @@ import { h } from '@financial-times/x-engine'; import Url from './Url'; +import Message from './Message'; import Buttons from './Buttons'; +import styles from './GiftArticle.css'; -export default ({ isGift, isGiftUrlCreated, url, mailtoUrl, createGiftUrl, tracking }) => ( - <div className="gift-form__radio-section" data-section-id="giftLink" data-trackable={ tracking }> - <div className="gift-form__create-link o-forms__affix-wrapper"> - <Url - isGift={ isGift } - isGiftUrlCreated={ isGiftUrlCreated } - url={ url }/> - <Buttons - isGift={ isGift } - isGiftUrlCreated={ isGiftUrlCreated } - mailtoUrl={ mailtoUrl } - createGiftUrl={ createGiftUrl }/> - </div> +export default ({ tracking, isGift, isGiftUrlCreated, url, credit, mailtoUrl, createGiftUrl }) => ( + <div className={ styles['url-section'] } data-section-id="giftLink" data-trackable={ tracking }> + <Url + isGift={ isGift } + isGiftUrlCreated={ isGiftUrlCreated } + url={ url }/> + <Message + isGift={ isGift } + isGiftUrlCreated={ isGiftUrlCreated } + credit={ credit }/> + <Buttons + isGift={ isGift } + isGiftUrlCreated={ isGiftUrlCreated } + mailtoUrl={ mailtoUrl } + createGiftUrl={ createGiftUrl }/> </div> ); From 7f1cd311c40d7c85814b976c586b056d650914ee Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 17 Jul 2018 16:49:50 +0100 Subject: [PATCH 068/760] =?UTF-8?q?set=20correct=20url=20type=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/package.json | 5 +++-- components/x-gift-article/src/GiftArticle.jsx | 17 +++++++++++++---- components/x-gift-article/src/Url.jsx | 6 +++--- components/x-gift-article/src/UrlSection.jsx | 5 +++-- .../x-gift-article/stories/with-gift-credits.js | 8 +++++--- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 2b022df81..a4ae46b3f 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -17,8 +17,9 @@ "dependencies": { "@financial-times/x-engine": "^1.0.0-1", "@financial-times/x-rollup": "^1.0.0", - "@financial-times/x-interaction": "^1.0.0-1", - "rollup": "^0.57.1" + "@financial-times/x-interaction": "^1.0.0-1", + "rollup": "^0.57.1", + "classnames": "^2.2.6" }, "peerDependencies": { "@financial-times/x-engine": "^1.0.0-1" diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index eb32ba03a..823f7bd6c 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -5,30 +5,38 @@ import RadioButtonsSection from './RadioButtonsSection'; import UrlSection from './UrlSection'; import styles from './GiftArticle.css'; +const urlTypeGift = 'gift-link'; +const urlTypeNonGift = 'non-gift-link'; +const trackingGift = 'giftLink'; +const trackingNonGift = 'nonGiftLink'; + let isGiftUrlCreated = false; -const withRadioButtonActions = withActions(({ url, giftUrl, nonGiftUrl, isGiftUrlCreated, mailtoGiftUrl, mailtoNonGiftUrl }) => ({ +const withRadioButtonActions = withActions(({ url, urlType, giftUrl, nonGiftUrl, isGiftUrlCreated, mailtoGiftUrl, mailtoNonGiftUrl }) => ({ displayGiftUrlSection() { return { isGift: true, url: isGiftUrlCreated ? giftUrl : url, + urlType: isGiftUrlCreated ? urlTypeGift : urlType, mailtoUrl: mailtoGiftUrl, - tracking: 'giftLink' + tracking: trackingGift } }, displayNonGiftUrlSection() { return { isGift: false, url: nonGiftUrl, + urlType: urlTypeNonGift, mailtoUrl: mailtoNonGiftUrl, - tracking: 'nonGiftLink' + tracking: trackingNonGift } }, createGiftUrl() { isGiftUrlCreated = true; return { - isGiftUrlCreated: true, + isGiftUrlCreated, url: giftUrl, + urlType: urlTypeGift, mailtoUrl: mailtoGiftUrl } } @@ -46,6 +54,7 @@ const BaseTemplate = (data) => ( isGift={ data.isGift } isGiftUrlCreated={ data.isGiftUrlCreated } url={ data.url } + urlType={ data.urlType } credit={ data.credit } mailtoUrl={ data.mailtoUrl } createGiftUrl={ data.actions.createGiftUrl }/> diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 19283cbe6..be7d04e75 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -6,12 +6,12 @@ const urlClassNames = [ styles.url ].join(' '); -export default ({ isGift, isGiftUrlCreated, url }) => { +export default ({ isGift, isGiftUrlCreated, url, urlType }) => { if (!isGift || isGiftUrlCreated) { - return (<input type="text" name="example-gift-link" value={ url } className={ urlClassNames }></input>); + return (<input type="text" name={ urlType } value={ url } className={ urlClassNames }></input>); } - return (<input type="text" name="example-gift-link" value={ url } className={ urlClassNames } disabled='disabled'></input>); + return (<input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled='disabled'></input>); }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index b43a41b55..f8eef04f6 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,12 +4,13 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ tracking, isGift, isGiftUrlCreated, url, credit, mailtoUrl, createGiftUrl }) => ( +export default ({ tracking, isGift, isGiftUrlCreated, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( <div className={ styles['url-section'] } data-section-id="giftLink" data-trackable={ tracking }> <Url isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } - url={ url }/> + url={ url } + urlType={ urlType }/> <Message isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index eac2e7383..60497e4de 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -7,9 +7,10 @@ exports.data = { monthlyAllowance: 20, dateText: 'May 1', isGift: true, - url: 'dummy-url', - giftUrl: 'gift-url', - nonGiftUrl: 'non-gift-url', + url: 'https://dummy-url', + urlType: 'example-gift-link', + giftUrl: 'https://gift-url', + nonGiftUrl: 'https://non-gift-url', mailtoUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', mailtoGiftUrl: 'mailto:?subject=title&body=giftMailtoUrl', mailtoNonGiftUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', @@ -26,6 +27,7 @@ exports.knobs = [ 'giftUrl', 'nonGiftUrl', 'url', + 'urlType', 'mailtoUrl', 'mailtoGiftUrl', 'mailtoNonGiftUrl', From a174bc869738ffc573507831c05d6f9af0ea4891 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 17 Jul 2018 17:03:21 +0100 Subject: [PATCH 069/760] =?UTF-8?q?tidy=20up=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/GiftArticle.css | 2 ++ components/x-gift-article/src/GiftArticle.jsx | 9 +++++++-- components/x-gift-article/stories/index.js | 1 - 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css index 84ae55f61..86102d5df 100644 --- a/components/x-gift-article/src/GiftArticle.css +++ b/components/x-gift-article/src/GiftArticle.css @@ -1,5 +1,6 @@ .container { font-family: MetricWeb,sans-serif; + max-width: 500px; } .bold { @@ -29,6 +30,7 @@ .url { grid-area: share-url; + max-width: none; } .message { diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 823f7bd6c..7e6c6f8a2 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -3,7 +3,12 @@ import { withActions } from '@financial-times/x-interaction'; import Title from './Title'; import RadioButtonsSection from './RadioButtonsSection'; import UrlSection from './UrlSection'; + import styles from './GiftArticle.css'; +const containerClassNames = [ + 'o-forms', + styles.container +].join(' '); const urlTypeGift = 'gift-link'; const urlTypeNonGift = 'non-gift-link'; @@ -43,8 +48,8 @@ const withRadioButtonActions = withActions(({ url, urlType, giftUrl, nonGiftUrl, })); const BaseTemplate = (data) => ( - <form name="gift-form" className={ styles.container }> - <fieldset className="o-forms"> + <form name="gift-form"> + <fieldset className={ containerClassNames }> <Title title={ data.title }/> <RadioButtonsSection displayGiftUrlSection={ data.actions.displayGiftUrlSection } diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index bcf415229..c0577f9b0 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -6,7 +6,6 @@ exports.dependencies = { 'o-fonts': '^3.0.0', 'o-buttons': '^5.13.1', 'o-forms': '^5.7.3', - 'o-typography': '^5.7.5' }; exports.stories = [ require('./with-gift-credits') From 387063b103b8cca4bb123fb14883ffe1e9d11473 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 17 Jul 2018 17:08:04 +0100 Subject: [PATCH 070/760] =?UTF-8?q?tidy=20up=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Message.jsx | 6 +++++- components/x-gift-article/src/Url.jsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 306314cf1..88b82f2c8 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -5,7 +5,11 @@ export default ({ isGift, isGiftUrlCreated, credit }) => { const messageSubscriber = 'This link can only be read by existing subscribers'; const messageCopyLimit = 'This link can be opened up to 3 times'; - const messageGiftCreditRemaining = [ 'You have ', <span className={ styles.bold }>{ credit } gift articles</span>, ' left this month' ]; + const messageGiftCreditRemaining = [ + 'You have ', + <span className={ styles.bold }>{ credit } gift articles</span>, + ' left this month' + ]; let message = messageSubscriber; diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index be7d04e75..66682e9b2 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -12,6 +12,6 @@ export default ({ isGift, isGiftUrlCreated, url, urlType }) => { return (<input type="text" name={ urlType } value={ url } className={ urlClassNames }></input>); } - return (<input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled='disabled'></input>); + return (<input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled='disabled'></input>); }; From 7cc4200bcf9ec87bb77513007f2f2135ba550de4 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 17 Jul 2018 17:29:19 +0100 Subject: [PATCH 071/760] =?UTF-8?q?fix=20a=20React=20warning=20about=20uni?= =?UTF-8?q?que=20key=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Message.jsx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 88b82f2c8..032cb478b 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -1,22 +1,18 @@ import { h } from '@financial-times/x-engine'; import styles from './GiftArticle.css'; -export default ({ isGift, isGiftUrlCreated, credit }) => { - - const messageSubscriber = 'This link can only be read by existing subscribers'; - const messageCopyLimit = 'This link can be opened up to 3 times'; - const messageGiftCreditRemaining = [ - 'You have ', - <span className={ styles.bold }>{ credit } gift articles</span>, - ' left this month' - ]; +const messageClassName = styles.message; +const boldTextClassName = styles.bold; - let message = messageSubscriber; +export default ({ isGift, isGiftUrlCreated, credit }) => { if (isGift) { - message = isGiftUrlCreated ? messageCopyLimit : messageGiftCreditRemaining; + if (isGiftUrlCreated) { + return (<div className={ messageClassName }>This link can be opened up to 3 times</div>); + } + return (<div className={ messageClassName }>You have <span className={ boldTextClassName }>{ credit } gift articles</span> left this month</div>); } - return (<div className={ styles.message }>{ message }</div>); + return (<div className={ messageClassName }>This link can only be read by existing subscribers</div>); }; From 3bbadfcaa563aa39ca635b1ac0639478e61afece Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 18 Jul 2018 14:04:35 +0100 Subject: [PATCH 072/760] =?UTF-8?q?add=20async=20function=20GetGiftUrl=20?= =?UTF-8?q?=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/GetGiftUrl.jsx | 10 +++++++ components/x-gift-article/src/GiftArticle.jsx | 26 +++++++++++-------- .../stories/with-gift-credits.js | 2 -- 3 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 components/x-gift-article/src/GetGiftUrl.jsx diff --git a/components/x-gift-article/src/GetGiftUrl.jsx b/components/x-gift-article/src/GetGiftUrl.jsx new file mode 100644 index 000000000..9736fc109 --- /dev/null +++ b/components/x-gift-article/src/GetGiftUrl.jsx @@ -0,0 +1,10 @@ +import { h } from '@financial-times/x-engine'; + +const delay = ms => new Promise(r => setTimeout(r, ms)); + +export default () => { + return delay(1000) + .then(() => { + return 'https://gift-url'; + }) +}; diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 7e6c6f8a2..0c2ba90c6 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -3,6 +3,7 @@ import { withActions } from '@financial-times/x-interaction'; import Title from './Title'; import RadioButtonsSection from './RadioButtonsSection'; import UrlSection from './UrlSection'; +import GetGiftUrl from './GetGiftUrl'; import styles from './GiftArticle.css'; const containerClassNames = [ @@ -15,14 +16,14 @@ const urlTypeNonGift = 'non-gift-link'; const trackingGift = 'giftLink'; const trackingNonGift = 'nonGiftLink'; -let isGiftUrlCreated = false; +let giftUrl = undefined; -const withRadioButtonActions = withActions(({ url, urlType, giftUrl, nonGiftUrl, isGiftUrlCreated, mailtoGiftUrl, mailtoNonGiftUrl }) => ({ +const withRadioButtonActions = withActions(({ url, urlType, nonGiftUrl, isGiftUrlCreated, mailtoGiftUrl, mailtoNonGiftUrl }) => ({ displayGiftUrlSection() { return { isGift: true, - url: isGiftUrlCreated ? giftUrl : url, - urlType: isGiftUrlCreated ? urlTypeGift : urlType, + url: giftUrl || url, + urlType: giftUrl ? urlTypeGift : urlType, mailtoUrl: mailtoGiftUrl, tracking: trackingGift } @@ -37,13 +38,16 @@ const withRadioButtonActions = withActions(({ url, urlType, giftUrl, nonGiftUrl, } }, createGiftUrl() { - isGiftUrlCreated = true; - return { - isGiftUrlCreated, - url: giftUrl, - urlType: urlTypeGift, - mailtoUrl: mailtoGiftUrl - } + return GetGiftUrl() + .then(url => { + giftUrl = url; + return { + isGiftUrlCreated: true, + url: giftUrl, + urlType: urlTypeGift, + mailtoUrl: mailtoGiftUrl + } + }) } })); diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 60497e4de..db1aeb473 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -9,7 +9,6 @@ exports.data = { isGift: true, url: 'https://dummy-url', urlType: 'example-gift-link', - giftUrl: 'https://gift-url', nonGiftUrl: 'https://non-gift-url', mailtoUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', mailtoGiftUrl: 'mailto:?subject=title&body=giftMailtoUrl', @@ -24,7 +23,6 @@ exports.knobs = [ 'isFreeArticle', 'credit', 'isGift', - 'giftUrl', 'nonGiftUrl', 'url', 'urlType', From 96faa6f0af8e40771eb9489b465967d30001be76 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 18 Jul 2018 14:17:22 +0100 Subject: [PATCH 073/760] =?UTF-8?q?make=20how=20to=20set=20url=20disabled?= =?UTF-8?q?=20value=20simpler=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Url.jsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 66682e9b2..3b6b421a3 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -6,12 +6,6 @@ const urlClassNames = [ styles.url ].join(' '); -export default ({ isGift, isGiftUrlCreated, url, urlType }) => { - - if (!isGift || isGiftUrlCreated) { - return (<input type="text" name={ urlType } value={ url } className={ urlClassNames }></input>); - } - - return (<input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled='disabled'></input>); - -}; +export default ({ isGift, isGiftUrlCreated, url, urlType }) => ( + <input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled={ isGift && !isGiftUrlCreated }></input> +); From 444405a831d88283e43e4f49fc715d3e34a6f368 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 18 Jul 2018 15:08:32 +0100 Subject: [PATCH 074/760] =?UTF-8?q?add=20Loading=20status=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/GetGiftUrl.jsx | 6 ++---- components/x-gift-article/src/GiftArticle.jsx | 1 + components/x-gift-article/src/Url.jsx | 6 +++--- components/x-gift-article/src/UrlSection.jsx | 3 ++- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/x-gift-article/src/GetGiftUrl.jsx b/components/x-gift-article/src/GetGiftUrl.jsx index 9736fc109..2659ed96a 100644 --- a/components/x-gift-article/src/GetGiftUrl.jsx +++ b/components/x-gift-article/src/GetGiftUrl.jsx @@ -3,8 +3,6 @@ import { h } from '@financial-times/x-engine'; const delay = ms => new Promise(r => setTimeout(r, ms)); export default () => { - return delay(1000) - .then(() => { - return 'https://gift-url'; - }) + return delay(2000) + .then(() => ('https://gift-url')) }; diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 0c2ba90c6..5da64ee2a 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -60,6 +60,7 @@ const BaseTemplate = (data) => ( displayNonGiftUrlSection={ data.actions.displayNonGiftUrlSection }/> <UrlSection tracking={ data.tracking } + isLoading={ data.isLoading } isGift={ data.isGift } isGiftUrlCreated={ data.isGiftUrlCreated } url={ data.url } diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 3b6b421a3..a54fcf834 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -6,6 +6,6 @@ const urlClassNames = [ styles.url ].join(' '); -export default ({ isGift, isGiftUrlCreated, url, urlType }) => ( - <input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled={ isGift && !isGiftUrlCreated }></input> -); +export default ({ isLoading, isGift, isGiftUrlCreated, url, urlType }) => { + return (<input type="text" name={ urlType } value={ isLoading ? 'Creating a gift url...' : url } className={ urlClassNames } disabled={ isGift && !isGiftUrlCreated }></input>); +}; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index f8eef04f6..61abeb44d 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,9 +4,10 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ tracking, isGift, isGiftUrlCreated, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( +export default ({ tracking, isLoading, isGift, isGiftUrlCreated, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( <div className={ styles['url-section'] } data-section-id="giftLink" data-trackable={ tracking }> <Url + isLoading={ isLoading } isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } url={ url } From e99c15f2c95ab7f174e1e14190e41ce5713eff6a Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 19 Jul 2018 14:37:34 +0100 Subject: [PATCH 075/760] =?UTF-8?q?add=20compose=20data=20function=20befor?= =?UTF-8?q?e=20loading=20gift=20article=20form=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Form.jsx | 30 +++++++ components/x-gift-article/src/GiftArticle.jsx | 83 +++++++++++-------- components/x-gift-article/src/Url.jsx | 4 +- components/x-gift-article/src/UrlSection.jsx | 3 +- .../src/lib/fetch-gift-credit.js | 8 ++ .../{GetGiftUrl.jsx => lib/get-gift-url.js} | 2 - .../x-gift-article/src/lib/get-mailto-link.js | 6 ++ .../stories/with-gift-credits.js | 27 +----- 8 files changed, 100 insertions(+), 63 deletions(-) create mode 100644 components/x-gift-article/src/Form.jsx create mode 100644 components/x-gift-article/src/lib/fetch-gift-credit.js rename components/x-gift-article/src/{GetGiftUrl.jsx => lib/get-gift-url.js} (74%) create mode 100644 components/x-gift-article/src/lib/get-mailto-link.js diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx new file mode 100644 index 000000000..48dea402f --- /dev/null +++ b/components/x-gift-article/src/Form.jsx @@ -0,0 +1,30 @@ +import { h } from '@financial-times/x-engine'; +import Title from './Title'; +import RadioButtonsSection from './RadioButtonsSection'; +import UrlSection from './UrlSection'; + +import styles from './GiftArticle.css'; +const containerClassNames = [ + 'o-forms', + styles.container +].join(' '); + +export default (data) => ( + <form name="gift-form"> + <fieldset className={ containerClassNames }> + <Title title={ data.title }/> + <RadioButtonsSection + displayGiftUrlSection={ data.actions.displayGiftUrlSection } + displayNonGiftUrlSection={ data.actions.displayNonGiftUrlSection }/> + <UrlSection + tracking={ data.tracking } + isGift={ data.isGift } + isGiftUrlCreated={ data.isGiftUrlCreated } + url={ data.url } + urlType={ data.urlType } + credit={ data.credit } + mailtoUrl={ data.mailtoUrl } + createGiftUrl={ data.actions.createGiftUrl }/> + </fieldset> + </form> +); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 5da64ee2a..8448053b0 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -1,29 +1,32 @@ import { h } from '@financial-times/x-engine'; import { withActions } from '@financial-times/x-interaction'; -import Title from './Title'; -import RadioButtonsSection from './RadioButtonsSection'; -import UrlSection from './UrlSection'; -import GetGiftUrl from './GetGiftUrl'; +import Form from './Form'; -import styles from './GiftArticle.css'; -const containerClassNames = [ - 'o-forms', - styles.container -].join(' '); +import getGiftUrl from './lib/get-gift-url'; +import getMailtoLink from './lib/get-mailto-link'; +import fetchGiftCredit from './lib/fetch-gift-credit'; const urlTypeGift = 'gift-link'; const urlTypeNonGift = 'non-gift-link'; +const urlTypeDefault = 'example-gift-link'; + const trackingGift = 'giftLink'; const trackingNonGift = 'nonGiftLink'; +const defaultUrl = 'https://dummy-url'; +const nonGiftUrl = 'https://non-gift-url'; + let giftUrl = undefined; +let mailtoGiftUrl; +let mailtoNonGiftUrl; +let hasAttempetedToFetchCredit = false; -const withRadioButtonActions = withActions(({ url, urlType, nonGiftUrl, isGiftUrlCreated, mailtoGiftUrl, mailtoNonGiftUrl }) => ({ +const withGiftFormActions = withActions(({ title, articleTitle, articleUrl }) => ({ displayGiftUrlSection() { return { isGift: true, - url: giftUrl || url, - urlType: giftUrl ? urlTypeGift : urlType, + url: giftUrl || defaultUrl, + urlType: giftUrl ? urlTypeGift : urlTypeDefault, mailtoUrl: mailtoGiftUrl, tracking: trackingGift } @@ -38,9 +41,11 @@ const withRadioButtonActions = withActions(({ url, urlType, nonGiftUrl, isGiftUr } }, createGiftUrl() { - return GetGiftUrl() + return getGiftUrl() .then(url => { giftUrl = url; + mailtoGiftUrl = getMailtoLink(articleTitle, giftUrl); + return { isGiftUrlCreated: true, url: giftUrl, @@ -48,30 +53,40 @@ const withRadioButtonActions = withActions(({ url, urlType, nonGiftUrl, isGiftUr mailtoUrl: mailtoGiftUrl } }) + }, + composeData() { + mailtoNonGiftUrl = getMailtoLink(articleTitle, articleUrl); + + const composedData = { + title: title || 'Share this article', + isGift: true, + url: defaultUrl, + urlType: urlTypeDefault, + mailtoUrl: mailtoNonGiftUrl, + isGiftUrlCreated: false, + tracking: trackingGift + }; + + return fetchGiftCredit() + .then(credit => { + composedData.credit = credit + return composedData; + }) + .catch(() => { + return composedData; + }) } })); -const BaseTemplate = (data) => ( - <form name="gift-form"> - <fieldset className={ containerClassNames }> - <Title title={ data.title }/> - <RadioButtonsSection - displayGiftUrlSection={ data.actions.displayGiftUrlSection } - displayNonGiftUrlSection={ data.actions.displayNonGiftUrlSection }/> - <UrlSection - tracking={ data.tracking } - isLoading={ data.isLoading } - isGift={ data.isGift } - isGiftUrlCreated={ data.isGiftUrlCreated } - url={ data.url } - urlType={ data.urlType } - credit={ data.credit } - mailtoUrl={ data.mailtoUrl } - createGiftUrl={ data.actions.createGiftUrl }/> - </fieldset> - </form> -); +const BaseTemplate = (data) => { + if (!hasAttempetedToFetchCredit) { + hasAttempetedToFetchCredit = true; + data.actions.composeData(); + } + + return <Form {...data}/>; +}; -const GiftArticle = withRadioButtonActions(BaseTemplate); +const GiftArticle = withGiftFormActions(BaseTemplate); export { GiftArticle }; diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index a54fcf834..19ee7d836 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -6,6 +6,6 @@ const urlClassNames = [ styles.url ].join(' '); -export default ({ isLoading, isGift, isGiftUrlCreated, url, urlType }) => { - return (<input type="text" name={ urlType } value={ isLoading ? 'Creating a gift url...' : url } className={ urlClassNames } disabled={ isGift && !isGiftUrlCreated }></input>); +export default ({ isGift, isGiftUrlCreated, url, urlType }) => { + return (<input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled={ isGift && !isGiftUrlCreated }></input>); }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 61abeb44d..f8eef04f6 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,10 +4,9 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ tracking, isLoading, isGift, isGiftUrlCreated, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( +export default ({ tracking, isGift, isGiftUrlCreated, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( <div className={ styles['url-section'] } data-section-id="giftLink" data-trackable={ tracking }> <Url - isLoading={ isLoading } isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } url={ url } diff --git a/components/x-gift-article/src/lib/fetch-gift-credit.js b/components/x-gift-article/src/lib/fetch-gift-credit.js new file mode 100644 index 000000000..f5ed64005 --- /dev/null +++ b/components/x-gift-article/src/lib/fetch-gift-credit.js @@ -0,0 +1,8 @@ +import { h } from '@financial-times/x-engine'; + +const delay = ms => new Promise(r => setTimeout(r, ms)); + +export default () => { + return delay(1000) + .then(() => (20)) +}; diff --git a/components/x-gift-article/src/GetGiftUrl.jsx b/components/x-gift-article/src/lib/get-gift-url.js similarity index 74% rename from components/x-gift-article/src/GetGiftUrl.jsx rename to components/x-gift-article/src/lib/get-gift-url.js index 2659ed96a..5aa24fa39 100644 --- a/components/x-gift-article/src/GetGiftUrl.jsx +++ b/components/x-gift-article/src/lib/get-gift-url.js @@ -1,5 +1,3 @@ -import { h } from '@financial-times/x-engine'; - const delay = ms => new Promise(r => setTimeout(r, ms)); export default () => { diff --git a/components/x-gift-article/src/lib/get-mailto-link.js b/components/x-gift-article/src/lib/get-mailto-link.js new file mode 100644 index 000000000..ce3ec29f2 --- /dev/null +++ b/components/x-gift-article/src/lib/get-mailto-link.js @@ -0,0 +1,6 @@ +export default (articleTitle, shareUrl) => { + const subject = encodeURIComponent(articleTitle); + const body = encodeURIComponent(shareUrl); + + return `mailto:?subject=${subject}&body=${body}`; +} diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index db1aeb473..94c8f732e 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -3,34 +3,15 @@ exports.title = 'With gift credits'; exports.data = { title: 'Share this article', isFreeArticle: false, - credit: 20, - monthlyAllowance: 20, - dateText: 'May 1', - isGift: true, - url: 'https://dummy-url', - urlType: 'example-gift-link', - nonGiftUrl: 'https://non-gift-url', - mailtoUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', - mailtoGiftUrl: 'mailto:?subject=title&body=giftMailtoUrl', - mailtoNonGiftUrl: 'mailto:?subject=title&body=nonGiftMailtoUrl', - isGiftUrlCreated: false, - tracking: 'giftLink' + articleUrl: 'https://www.ft.com/content/blahblah', + articleTitle: 'Title Title Title Title', }; exports.knobs = [ - 'showTitle', 'title', 'isFreeArticle', - 'credit', - 'isGift', - 'nonGiftUrl', - 'url', - 'urlType', - 'mailtoUrl', - 'mailtoGiftUrl', - 'mailtoNonGiftUrl', - 'isGiftUrlCreated', - 'tracking' + 'articleUrl', + 'articleTitle' ]; // This reference is only required for hot module loading in development From 144303e6a8da6339200e23992cbc883474660107 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 19 Jul 2018 16:31:31 +0100 Subject: [PATCH 076/760] =?UTF-8?q?add=20Loading=20spinner=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Form.jsx | 1 + components/x-gift-article/src/GiftArticle.css | 9 +++++++++ components/x-gift-article/src/GiftArticle.jsx | 3 ++- components/x-gift-article/src/Loading.jsx | 8 ++++++++ components/x-gift-article/src/RadioButtonsSection.jsx | 6 +++--- components/x-gift-article/src/Url.jsx | 2 +- components/x-gift-article/stories/index.js | 1 + 7 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 components/x-gift-article/src/Loading.jsx diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 48dea402f..4b8d656e6 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -14,6 +14,7 @@ export default (data) => ( <fieldset className={ containerClassNames }> <Title title={ data.title }/> <RadioButtonsSection + isGift={ data.isGift } displayGiftUrlSection={ data.actions.displayGiftUrlSection } displayNonGiftUrlSection={ data.actions.displayNonGiftUrlSection }/> <UrlSection diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css index 86102d5df..fd654a989 100644 --- a/components/x-gift-article/src/GiftArticle.css +++ b/components/x-gift-article/src/GiftArticle.css @@ -49,3 +49,12 @@ .button--with-gap { margin-right: 5px; } + +.loading-spinner__container { + width: 100%; + height: 100%; + min-height: 200px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 8448053b0..7864086d0 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -1,5 +1,6 @@ import { h } from '@financial-times/x-engine'; import { withActions } from '@financial-times/x-interaction'; +import Loading from './Loading'; import Form from './Form'; import getGiftUrl from './lib/get-gift-url'; @@ -84,7 +85,7 @@ const BaseTemplate = (data) => { data.actions.composeData(); } - return <Form {...data}/>; + return data.isLoading ? <Loading/> : <Form {...data}/>; }; const GiftArticle = withGiftFormActions(BaseTemplate); diff --git a/components/x-gift-article/src/Loading.jsx b/components/x-gift-article/src/Loading.jsx new file mode 100644 index 000000000..5952bb186 --- /dev/null +++ b/components/x-gift-article/src/Loading.jsx @@ -0,0 +1,8 @@ +import { h } from '@financial-times/x-engine'; +import styles from './GiftArticle.css'; + +export default () => ( + <div class={ styles['loading-spinner__container'] }> + <div class="o-loading o-loading--dark o-loading--large"></div> + </div> +); diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index eb28e6c37..e95102e92 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -8,13 +8,13 @@ const radioSectionClassNames = [ styles['radio-button-section'] ].join(' '); -export default ({ displayGiftUrlSection, displayNonGiftUrlSection }) => ( +export default ({ isGift, displayGiftUrlSection, displayNonGiftUrlSection }) => ( <div className={ radioSectionClassNames }> - <input type="radio" name="gift-form__radio" value="giftLink" className="o-forms__radio" id="giftLink" defaultChecked onChange={ displayGiftUrlSection }/> + <input type="radio" name="gift-form__radio" value="giftLink" className="o-forms__radio" id="giftLink" checked={ isGift } onChange={ displayGiftUrlSection }/> <label htmlFor="giftLink" className="o-forms__label">with <span className={ boldTextClassName }>anyone</span></label> - <input type="radio" name="gift-form__radio" value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" onChange={ displayNonGiftUrlSection }/> + <input type="radio" name="gift-form__radio" value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" checked={ !isGift } onChange={ displayNonGiftUrlSection }/> <label htmlFor="nonGiftLink" className="o-forms__label">with <span className={ boldTextClassName }>other subscribers</span></label> </div> diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 19ee7d836..5ad46b792 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -7,5 +7,5 @@ const urlClassNames = [ ].join(' '); export default ({ isGift, isGiftUrlCreated, url, urlType }) => { - return (<input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled={ isGift && !isGiftUrlCreated }></input>); + return (<input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled={ isGift && !isGiftUrlCreated } readOnly></input>); }; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index c0577f9b0..84769a233 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -6,6 +6,7 @@ exports.dependencies = { 'o-fonts': '^3.0.0', 'o-buttons': '^5.13.1', 'o-forms': '^5.7.3', + 'o-loading': '^2.2.2' }; exports.stories = [ require('./with-gift-credits') From 5ecf52527d8dd4df53cd36de44f3a1665e1f2ebc Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 19 Jul 2018 16:33:54 +0100 Subject: [PATCH 077/760] =?UTF-8?q?data=20=3D>=20props=20=20=F0=9F=90=BF?= =?UTF-8?q?=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Form.jsx | 26 +++++++++---------- components/x-gift-article/src/GiftArticle.jsx | 6 ++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 4b8d656e6..938795583 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -9,23 +9,23 @@ const containerClassNames = [ styles.container ].join(' '); -export default (data) => ( +export default (props) => ( <form name="gift-form"> <fieldset className={ containerClassNames }> - <Title title={ data.title }/> + <Title title={ props.title }/> <RadioButtonsSection - isGift={ data.isGift } - displayGiftUrlSection={ data.actions.displayGiftUrlSection } - displayNonGiftUrlSection={ data.actions.displayNonGiftUrlSection }/> + isGift={ props.isGift } + displayGiftUrlSection={ props.actions.displayGiftUrlSection } + displayNonGiftUrlSection={ props.actions.displayNonGiftUrlSection }/> <UrlSection - tracking={ data.tracking } - isGift={ data.isGift } - isGiftUrlCreated={ data.isGiftUrlCreated } - url={ data.url } - urlType={ data.urlType } - credit={ data.credit } - mailtoUrl={ data.mailtoUrl } - createGiftUrl={ data.actions.createGiftUrl }/> + tracking={ props.tracking } + isGift={ props.isGift } + isGiftUrlCreated={ props.isGiftUrlCreated } + url={ props.url } + urlType={ props.urlType } + credit={ props.credit } + mailtoUrl={ props.mailtoUrl } + createGiftUrl={ props.actions.createGiftUrl }/> </fieldset> </form> ); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 7864086d0..3868da513 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -79,13 +79,13 @@ const withGiftFormActions = withActions(({ title, articleTitle, articleUrl }) => } })); -const BaseTemplate = (data) => { +const BaseTemplate = (props) => { if (!hasAttempetedToFetchCredit) { hasAttempetedToFetchCredit = true; - data.actions.composeData(); + props.actions.composeData(); } - return data.isLoading ? <Loading/> : <Form {...data}/>; + return props.isLoading ? <Loading/> : <Form {...props}/>; }; const GiftArticle = withGiftFormActions(BaseTemplate); From 54474df6c9ae4c8b520e4858aa5df0c3647432aa Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 19 Jul 2018 16:44:17 +0100 Subject: [PATCH 078/760] =?UTF-8?q?delete=20unnecessary=20line=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/lib/fetch-gift-credit.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/x-gift-article/src/lib/fetch-gift-credit.js b/components/x-gift-article/src/lib/fetch-gift-credit.js index f5ed64005..07ee13d46 100644 --- a/components/x-gift-article/src/lib/fetch-gift-credit.js +++ b/components/x-gift-article/src/lib/fetch-gift-credit.js @@ -1,5 +1,3 @@ -import { h } from '@financial-times/x-engine'; - const delay = ms => new Promise(r => setTimeout(r, ms)); export default () => { From 303d5f6c566a596b2c197459ac4b1f9e4b798ecd Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 19 Jul 2018 16:50:43 +0100 Subject: [PATCH 079/760] =?UTF-8?q?class=20=3D>=20className=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Loading.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-gift-article/src/Loading.jsx b/components/x-gift-article/src/Loading.jsx index 5952bb186..a7d2b37e0 100644 --- a/components/x-gift-article/src/Loading.jsx +++ b/components/x-gift-article/src/Loading.jsx @@ -2,7 +2,7 @@ import { h } from '@financial-times/x-engine'; import styles from './GiftArticle.css'; export default () => ( - <div class={ styles['loading-spinner__container'] }> - <div class="o-loading o-loading--dark o-loading--large"></div> + <div className={ styles['loading-spinner__container'] }> + <div className="o-loading o-loading--dark o-loading--large"></div> </div> ); From b204de48eb05fdae0f2582f697d84607998f9ce8 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 08:34:39 +0100 Subject: [PATCH 080/760] =?UTF-8?q?free=20article=20=20=F0=9F=90=BF=20v2.1?= =?UTF-8?q?0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Form.jsx | 4 +++- components/x-gift-article/src/GiftArticle.jsx | 12 ++++++------ components/x-gift-article/src/Message.jsx | 6 +++++- components/x-gift-article/src/UrlSection.jsx | 3 ++- .../x-gift-article/stories/free-article.js | 19 +++++++++++++++++++ components/x-gift-article/stories/index.js | 3 ++- 6 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 components/x-gift-article/stories/free-article.js diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 938795583..c4e512018 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -13,14 +13,16 @@ export default (props) => ( <form name="gift-form"> <fieldset className={ containerClassNames }> <Title title={ props.title }/> - <RadioButtonsSection + { props.isFreeArticle ? null : <RadioButtonsSection isGift={ props.isGift } displayGiftUrlSection={ props.actions.displayGiftUrlSection } displayNonGiftUrlSection={ props.actions.displayNonGiftUrlSection }/> + } <UrlSection tracking={ props.tracking } isGift={ props.isGift } isGiftUrlCreated={ props.isGiftUrlCreated } + isFreeArticle={ props.isFreeArticle } url={ props.url } urlType={ props.urlType } credit={ props.credit } diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 3868da513..6e5145953 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -22,7 +22,7 @@ let mailtoGiftUrl; let mailtoNonGiftUrl; let hasAttempetedToFetchCredit = false; -const withGiftFormActions = withActions(({ title, articleTitle, articleUrl }) => ({ +const withGiftFormActions = withActions(({ isFreeArticle, title, articleTitle, articleUrl }) => ({ displayGiftUrlSection() { return { isGift: true, @@ -60,12 +60,12 @@ const withGiftFormActions = withActions(({ title, articleTitle, articleUrl }) => const composedData = { title: title || 'Share this article', - isGift: true, - url: defaultUrl, - urlType: urlTypeDefault, - mailtoUrl: mailtoNonGiftUrl, + isGift: isFreeArticle ? false : true, + url: isFreeArticle ? nonGiftUrl : defaultUrl, + urlType: isFreeArticle ? urlTypeNonGift : urlTypeDefault, + mailtoUrl: isFreeArticle ? mailtoNonGiftUrl : undefined, isGiftUrlCreated: false, - tracking: trackingGift + tracking: isFreeArticle ? trackingNonGift : trackingGift }; return fetchGiftCredit() diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 032cb478b..beafb82e7 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -4,7 +4,11 @@ import styles from './GiftArticle.css'; const messageClassName = styles.message; const boldTextClassName = styles.bold; -export default ({ isGift, isGiftUrlCreated, credit }) => { +export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit }) => { + + if (isFreeArticle) { + return (<div className={ messageClassName }>This article is currently <span className={ boldTextClassName }>free</span> for anyone to read</div>); + } if (isGift) { if (isGiftUrlCreated) { diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index f8eef04f6..61f2c21b0 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,7 +4,7 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ tracking, isGift, isGiftUrlCreated, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( +export default ({ tracking, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( <div className={ styles['url-section'] } data-section-id="giftLink" data-trackable={ tracking }> <Url isGift={ isGift } @@ -14,6 +14,7 @@ export default ({ tracking, isGift, isGiftUrlCreated, url, urlType, credit, mail <Message isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } + isFreeArticle={ isFreeArticle } credit={ credit }/> <Buttons isGift={ isGift } diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js new file mode 100644 index 000000000..6bf5be487 --- /dev/null +++ b/components/x-gift-article/stories/free-article.js @@ -0,0 +1,19 @@ +exports.title = 'Free article'; + +exports.data = { + title: 'Share this article', + isFreeArticle: true, + articleUrl: 'https://www.ft.com/content/blahblah', + articleTitle: 'Title Title Title Title', +}; + +exports.knobs = [ + 'title', + 'isFreeArticle', + 'articleUrl', + 'articleTitle' +]; + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 84769a233..0c6a57368 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -9,6 +9,7 @@ exports.dependencies = { 'o-loading': '^2.2.2' }; exports.stories = [ - require('./with-gift-credits') + require('./with-gift-credits'), + require('./free-article') ]; exports.knobs = require('./knobs'); From 2262324b4e7fb86be5550c433a9ae86123cde691 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 08:49:30 +0100 Subject: [PATCH 081/760] =?UTF-8?q?tracking=20=3D>=20type=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Form.jsx | 2 +- components/x-gift-article/src/GiftArticle.jsx | 10 +++++----- components/x-gift-article/src/UrlSection.jsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index c4e512018..02a28527d 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -19,7 +19,7 @@ export default (props) => ( displayNonGiftUrlSection={ props.actions.displayNonGiftUrlSection }/> } <UrlSection - tracking={ props.tracking } + type={ props.type } isGift={ props.isGift } isGiftUrlCreated={ props.isGiftUrlCreated } isFreeArticle={ props.isFreeArticle } diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 6e5145953..7ba739481 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -11,8 +11,8 @@ const urlTypeGift = 'gift-link'; const urlTypeNonGift = 'non-gift-link'; const urlTypeDefault = 'example-gift-link'; -const trackingGift = 'giftLink'; -const trackingNonGift = 'nonGiftLink'; +const typeGift = 'giftLink'; +const typeNonGift = 'nonGiftLink'; const defaultUrl = 'https://dummy-url'; const nonGiftUrl = 'https://non-gift-url'; @@ -29,7 +29,7 @@ const withGiftFormActions = withActions(({ isFreeArticle, title, articleTitle, a url: giftUrl || defaultUrl, urlType: giftUrl ? urlTypeGift : urlTypeDefault, mailtoUrl: mailtoGiftUrl, - tracking: trackingGift + type: typeGift } }, displayNonGiftUrlSection() { @@ -38,7 +38,7 @@ const withGiftFormActions = withActions(({ isFreeArticle, title, articleTitle, a url: nonGiftUrl, urlType: urlTypeNonGift, mailtoUrl: mailtoNonGiftUrl, - tracking: trackingNonGift + type: typeNonGift } }, createGiftUrl() { @@ -65,7 +65,7 @@ const withGiftFormActions = withActions(({ isFreeArticle, title, articleTitle, a urlType: isFreeArticle ? urlTypeNonGift : urlTypeDefault, mailtoUrl: isFreeArticle ? mailtoNonGiftUrl : undefined, isGiftUrlCreated: false, - tracking: isFreeArticle ? trackingNonGift : trackingGift + type: isFreeArticle ? typeNonGift : typeGift }; return fetchGiftCredit() diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 61f2c21b0..3f7f626c3 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,8 +4,8 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ tracking, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( - <div className={ styles['url-section'] } data-section-id="giftLink" data-trackable={ tracking }> +export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( + <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> <Url isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } From ba5c79d5729887334704d793c19a13f836197d85 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 08:52:15 +0100 Subject: [PATCH 082/760] =?UTF-8?q?getMailtoLink=20=3D>=20createMailtoLink?= =?UTF-8?q?=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/GiftArticle.jsx | 6 +++--- .../src/lib/{get-mailto-link.js => create-mailto-link.js} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename components/x-gift-article/src/lib/{get-mailto-link.js => create-mailto-link.js} (100%) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 7ba739481..8baf42039 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -4,7 +4,7 @@ import Loading from './Loading'; import Form from './Form'; import getGiftUrl from './lib/get-gift-url'; -import getMailtoLink from './lib/get-mailto-link'; +import createMailtoLink from './lib/create-mailto-link'; import fetchGiftCredit from './lib/fetch-gift-credit'; const urlTypeGift = 'gift-link'; @@ -45,7 +45,7 @@ const withGiftFormActions = withActions(({ isFreeArticle, title, articleTitle, a return getGiftUrl() .then(url => { giftUrl = url; - mailtoGiftUrl = getMailtoLink(articleTitle, giftUrl); + mailtoGiftUrl = createMailtoLink(articleTitle, giftUrl); return { isGiftUrlCreated: true, @@ -56,7 +56,7 @@ const withGiftFormActions = withActions(({ isFreeArticle, title, articleTitle, a }) }, composeData() { - mailtoNonGiftUrl = getMailtoLink(articleTitle, articleUrl); + mailtoNonGiftUrl = createMailtoLink(articleTitle, articleUrl); const composedData = { title: title || 'Share this article', diff --git a/components/x-gift-article/src/lib/get-mailto-link.js b/components/x-gift-article/src/lib/create-mailto-link.js similarity index 100% rename from components/x-gift-article/src/lib/get-mailto-link.js rename to components/x-gift-article/src/lib/create-mailto-link.js From 4d66d7b1d6268024296da4bfab8b18e369163f96 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 10:35:02 +0100 Subject: [PATCH 083/760] =?UTF-8?q?move=20all=20props=20related=20stuff=20?= =?UTF-8?q?to=20propsComposer=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Buttons.jsx | 4 +- components/x-gift-article/src/Form.jsx | 2 +- components/x-gift-article/src/GiftArticle.jsx | 68 ++++--------------- components/x-gift-article/src/UrlSection.jsx | 4 +- .../x-gift-article/stories/free-article.js | 1 - .../stories/with-gift-credits.js | 1 - 6 files changed, 20 insertions(+), 60 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index a0590ae71..2c9f66817 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -16,13 +16,13 @@ const ButtonWithGapClassNames = [ ].join(' '); -export default ({ isGift, isGiftUrlCreated, mailtoUrl, createGiftUrl }) => { +export default ({ isGift, isGiftUrlCreated, mailtoLink, createGiftUrl }) => { if (isGiftUrlCreated || !isGift) { return ( <div className={ ButtonsClassName }> <button className={ ButtonWithGapClassNames } type="button">Copy link</button> - <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank">Email link</a> + <a className={ ButtonClassNames } href={ mailtoLink } target="_blank">Email link</a> </div> ); } diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 02a28527d..595f049e9 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -26,7 +26,7 @@ export default (props) => ( url={ props.url } urlType={ props.urlType } credit={ props.credit } - mailtoUrl={ props.mailtoUrl } + mailtoLink={ props.mailtoLink } createGiftUrl={ props.actions.createGiftUrl }/> </fieldset> </form> diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 8baf42039..6c7414733 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -4,84 +4,46 @@ import Loading from './Loading'; import Form from './Form'; import getGiftUrl from './lib/get-gift-url'; -import createMailtoLink from './lib/create-mailto-link'; import fetchGiftCredit from './lib/fetch-gift-credit'; +import { GiftArticlePropsComposer } from './lib/props-composer'; -const urlTypeGift = 'gift-link'; -const urlTypeNonGift = 'non-gift-link'; -const urlTypeDefault = 'example-gift-link'; - -const typeGift = 'giftLink'; -const typeNonGift = 'nonGiftLink'; - -const defaultUrl = 'https://dummy-url'; -const nonGiftUrl = 'https://non-gift-url'; - -let giftUrl = undefined; -let mailtoGiftUrl; -let mailtoNonGiftUrl; let hasAttempetedToFetchCredit = false; +let propsComposer; -const withGiftFormActions = withActions(({ isFreeArticle, title, articleTitle, articleUrl }) => ({ +const withGiftFormActions = withActions({ displayGiftUrlSection() { - return { - isGift: true, - url: giftUrl || defaultUrl, - urlType: giftUrl ? urlTypeGift : urlTypeDefault, - mailtoUrl: mailtoGiftUrl, - type: typeGift - } + return propsComposer.displayGiftUrlSection(); }, + displayNonGiftUrlSection() { - return { - isGift: false, - url: nonGiftUrl, - urlType: urlTypeNonGift, - mailtoUrl: mailtoNonGiftUrl, - type: typeNonGift - } + return propsComposer.displayNonGiftUrlSection(); }, + createGiftUrl() { return getGiftUrl() .then(url => { - giftUrl = url; - mailtoGiftUrl = createMailtoLink(articleTitle, giftUrl); - - return { - isGiftUrlCreated: true, - url: giftUrl, - urlType: urlTypeGift, - mailtoUrl: mailtoGiftUrl - } + return propsComposer.createGiftUrl(url); }) }, - composeData() { - mailtoNonGiftUrl = createMailtoLink(articleTitle, articleUrl); - const composedData = { - title: title || 'Share this article', - isGift: isFreeArticle ? false : true, - url: isFreeArticle ? nonGiftUrl : defaultUrl, - urlType: isFreeArticle ? urlTypeNonGift : urlTypeDefault, - mailtoUrl: isFreeArticle ? mailtoNonGiftUrl : undefined, - isGiftUrlCreated: false, - type: isFreeArticle ? typeNonGift : typeGift - }; + composeData() { + const composedProps = propsComposer.getDefault(); return fetchGiftCredit() .then(credit => { - composedData.credit = credit - return composedData; + composedProps.credit = credit + return composedProps; }) .catch(() => { - return composedData; + return composedProps; }) } -})); +}); const BaseTemplate = (props) => { if (!hasAttempetedToFetchCredit) { hasAttempetedToFetchCredit = true; + propsComposer = new GiftArticlePropsComposer(props); props.actions.composeData(); } diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 3f7f626c3..dd69b8a42 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,7 +4,7 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, mailtoUrl, createGiftUrl }) => ( +export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, mailtoLink, createGiftUrl }) => ( <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> <Url isGift={ isGift } @@ -19,7 +19,7 @@ export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, c <Buttons isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } - mailtoUrl={ mailtoUrl } + mailtoLink={ mailtoLink } createGiftUrl={ createGiftUrl }/> </div> ); diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index 6bf5be487..ff6912f2f 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -1,7 +1,6 @@ exports.title = 'Free article'; exports.data = { - title: 'Share this article', isFreeArticle: true, articleUrl: 'https://www.ft.com/content/blahblah', articleTitle: 'Title Title Title Title', diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 94c8f732e..258de5e83 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -1,7 +1,6 @@ exports.title = 'With gift credits'; exports.data = { - title: 'Share this article', isFreeArticle: false, articleUrl: 'https://www.ft.com/content/blahblah', articleTitle: 'Title Title Title Title', From 8d3f29356468440e7bf289f3daf7826fbae29162 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 10:36:44 +0100 Subject: [PATCH 084/760] =?UTF-8?q?add=20propsComposer=20=20=F0=9F=90=BF?= =?UTF-8?q?=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x-gift-article/src/lib/props-composer.js | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 components/x-gift-article/src/lib/props-composer.js diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js new file mode 100644 index 000000000..23c427842 --- /dev/null +++ b/components/x-gift-article/src/lib/props-composer.js @@ -0,0 +1,77 @@ +import createMailtoLink from './create-mailto-link'; + +export class GiftArticlePropsComposer { + constructor({isFreeArticle, articleTitle, articleUrl}) { + this.isFreeArticle = isFreeArticle; + this.articleTitle = articleTitle; + this.articleUrl = articleUrl; + this.isGiftUrlCreated = false; + + this.urls = { + dummy: 'https://dummy-url', + gift: undefined, + nonGift: 'https://non-gift-url' + }; + + this.urlTypes = { + dummy: 'example-gift-link', + gift: 'gift-link', + nonGift: 'non-gift-link' + } + + this.mailtoLinks = { + gift: undefined, + nonGift: createMailtoLink(this.articleTitle, this.articleUrl) + } + + this.types = { + gift: 'giftLink', + nonGift: 'nonGiftLink' + } + } + + getDefault() { + return { + title: 'Share this article', + isGift: this.isFreeArticle ? false : true, + url: this.isFreeArticle ? this.urls.nonGift : this.urls.dummy, + urlType: this.isFreeArticle ? this.urlTypes.nonGift : this.urlTypes.dummy, + mailtoLink: this.isFreeArticle ? this.mailtoLinks.nonGift : undefined, + isGiftUrlCreated: this.isGiftUrlCreated, + type: this.isFreeArticle ? this.types.nonGift : this.types.gift + }; + } + + displayGiftUrlSection() { + return { + isGift: true, + url: this.urls.gift || this.urls.dummy, + urlType: this.urls.gift ? this.urlTypes.gift : this.urlTypes.dummy, + mailtoLink: this.mailtoLinks.gift, + type: this.types.gift + } + } + + displayNonGiftUrlSection() { + return { + isGift: false, + url: this.urls.nonGift, + urlType: this.urlTypes.nonGift, + mailtoLink: this.mailtoLinks.nonGift, + type: this.types.nonGift + } + } + + createGiftUrl(url) { + this.urls.gift = url; + this.isGiftUrlCreated = true; + this.mailtoLinks.gift = createMailtoLink(this.articleTitle, url); + + return { + isGiftUrlCreated: this.isGiftUrlCreated, + url: this.urls.gift, + urlType: this.urlTypes.gift, + mailtoLink: this.mailtoLinks.gift + } + } +} From f0f4844997823af541a8a30b985ede78e36b4e09 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 12:28:15 +0100 Subject: [PATCH 085/760] =?UTF-8?q?add=20GiftArticleWrapper=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/package.json | 6 +++--- components/x-gift-article/rollup.config.js | 2 +- components/x-gift-article/src/GiftArticle.jsx | 21 +++++++++---------- .../x-gift-article/src/GiftArticleWrapper.jsx | 13 ++++++++++++ .../x-gift-article/src/lib/props-composer.js | 8 ++++--- .../x-gift-article/stories/free-article.js | 1 + components/x-gift-article/stories/index.js | 4 ++-- .../stories/with-gift-credits.js | 1 + 8 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 components/x-gift-article/src/GiftArticleWrapper.jsx diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index a4ae46b3f..d99f7052c 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -2,9 +2,9 @@ "name": "@financial-times/x-gift-article", "version": "1.0.0", "description": "This module provides templates for gift article form", - "main": "dist/GiftArticle.cjs.js", - "browser": "dist/GiftArticle.es5.js", - "module": "dist/GiftArticle.esm.js", + "main": "dist/GiftArticleWrapper.cjs.js", + "browser": "dist/GiftArticleWrapper.es5.js", + "module": "dist/GiftArticleWrapper.esm.js", "style": "dist/GiftArticle.css", "scripts": { "prepare": "npm run build", diff --git a/components/x-gift-article/rollup.config.js b/components/x-gift-article/rollup.config.js index c303f0745..8f0f25c75 100644 --- a/components/x-gift-article/rollup.config.js +++ b/components/x-gift-article/rollup.config.js @@ -1,6 +1,6 @@ import xRollup from '@financial-times/x-rollup'; import pkg from './package.json'; -const input = 'src/GiftArticle.jsx'; +const input = 'src/GiftArticleWrapper.jsx'; export default xRollup({input, pkg}); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 6c7414733..b5dd9184c 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -5,7 +5,6 @@ import Form from './Form'; import getGiftUrl from './lib/get-gift-url'; import fetchGiftCredit from './lib/fetch-gift-credit'; -import { GiftArticlePropsComposer } from './lib/props-composer'; let hasAttempetedToFetchCredit = false; let propsComposer; @@ -22,29 +21,29 @@ const withGiftFormActions = withActions({ createGiftUrl() { return getGiftUrl() .then(url => { - return propsComposer.createGiftUrl(url); + return propsComposer.setGiftUrl(url); }) }, - composeData() { - const composedProps = propsComposer.getDefault(); - + fetchCredit() { return fetchGiftCredit() .then(credit => { - composedProps.credit = credit - return composedProps; + return { credit }; }) .catch(() => { - return composedProps; + // do something }) } }); const BaseTemplate = (props) => { - if (!hasAttempetedToFetchCredit) { + if (!propsComposer) { + propsComposer = props.composer; + } + + if (!hasAttempetedToFetchCredit && !props.isFreeArticle) { hasAttempetedToFetchCredit = true; - propsComposer = new GiftArticlePropsComposer(props); - props.actions.composeData(); + props.actions.fetchCredit(); } return props.isLoading ? <Loading/> : <Form {...props}/>; diff --git a/components/x-gift-article/src/GiftArticleWrapper.jsx b/components/x-gift-article/src/GiftArticleWrapper.jsx new file mode 100644 index 000000000..6dcb051a3 --- /dev/null +++ b/components/x-gift-article/src/GiftArticleWrapper.jsx @@ -0,0 +1,13 @@ +import { h } from '@financial-times/x-engine'; +import { GiftArticlePropsComposer } from './lib/props-composer'; +import { GiftArticle } from './GiftArticle'; + +const GiftArticleWrapper = (props) => { + const propsComposer = new GiftArticlePropsComposer(props); + const composedProps = propsComposer.getDefault(); + composedProps.composer = propsComposer; + + return <GiftArticle {...composedProps}/>; +};; + +export { GiftArticleWrapper }; diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 23c427842..4c4deb8f9 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -1,7 +1,8 @@ import createMailtoLink from './create-mailto-link'; export class GiftArticlePropsComposer { - constructor({isFreeArticle, articleTitle, articleUrl}) { + constructor({title, isFreeArticle, articleTitle, articleUrl}) { + this.title = title || 'Share this article'; this.isFreeArticle = isFreeArticle; this.articleTitle = articleTitle; this.articleUrl = articleUrl; @@ -32,7 +33,8 @@ export class GiftArticlePropsComposer { getDefault() { return { - title: 'Share this article', + title: this.title, + isFreeArticle: this.isFreeArticle, isGift: this.isFreeArticle ? false : true, url: this.isFreeArticle ? this.urls.nonGift : this.urls.dummy, urlType: this.isFreeArticle ? this.urlTypes.nonGift : this.urlTypes.dummy, @@ -62,7 +64,7 @@ export class GiftArticlePropsComposer { } } - createGiftUrl(url) { + setGiftUrl(url) { this.urls.gift = url; this.isGiftUrlCreated = true; this.mailtoLinks.gift = createMailtoLink(this.articleTitle, url); diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index ff6912f2f..a603e7d10 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -1,6 +1,7 @@ exports.title = 'Free article'; exports.data = { + title: 'Share this article (free)', isFreeArticle: true, articleUrl: 'https://www.ft.com/content/blahblah', articleTitle: 'Title Title Title Title', diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 0c6a57368..602cdcdc6 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -1,6 +1,6 @@ -const { GiftArticle } = require('../'); +const { GiftArticleWrapper } = require('../'); -exports.component = GiftArticle; +exports.component = GiftArticleWrapper; exports.package = require('../package.json'); exports.dependencies = { 'o-fonts': '^3.0.0', diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 258de5e83..94a231be6 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -1,6 +1,7 @@ exports.title = 'With gift credits'; exports.data = { + title: 'Share this article (with credit)', isFreeArticle: false, articleUrl: 'https://www.ft.com/content/blahblah', articleTitle: 'Title Title Title Title', From 5962361123ddaec1869629d8781e170a07a76b29 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 14:19:18 +0100 Subject: [PATCH 086/760] rebase master to reflect the change lerna => athloi --- components/x-gift-article/package.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index d99f7052c..a854be575 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -15,13 +15,10 @@ "author": "", "license": "ISC", "dependencies": { - "@financial-times/x-engine": "^1.0.0-1", - "@financial-times/x-rollup": "^1.0.0", - "@financial-times/x-interaction": "^1.0.0-1", + "@financial-times/x-engine": "file:../../packages/x-engine", + "@financial-times/x-rollup": "file:../../packages/x-rollup", + "@financial-times/x-interaction": "file:../x-interaction", "rollup": "^0.57.1", "classnames": "^2.2.6" - }, - "peerDependencies": { - "@financial-times/x-engine": "^1.0.0-1" } } From 98938bccc28a2a53654d43a1af5695b26a8140db Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 16:05:38 +0100 Subject: [PATCH 087/760] add no credit situation --- components/x-gift-article/src/Form.jsx | 2 ++ components/x-gift-article/src/GiftArticle.jsx | 28 +++++++++++-------- components/x-gift-article/src/Message.jsx | 11 +++++++- components/x-gift-article/src/UrlSection.jsx | 15 +++++++--- components/x-gift-article/src/lib/api.js | 12 ++++++++ .../src/lib/fetch-gift-credit.js | 6 ---- .../x-gift-article/src/lib/get-gift-url.js | 6 ---- .../src/lib/get-next-allowance-date.js | 12 ++++++++ 8 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 components/x-gift-article/src/lib/api.js delete mode 100644 components/x-gift-article/src/lib/fetch-gift-credit.js delete mode 100644 components/x-gift-article/src/lib/get-gift-url.js create mode 100644 components/x-gift-article/src/lib/get-next-allowance-date.js diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 595f049e9..18287ff0b 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -26,6 +26,8 @@ export default (props) => ( url={ props.url } urlType={ props.urlType } credit={ props.credit } + monthlyAllowance={ props.monthlyAllowance } + dateText={ props.dateText } mailtoLink={ props.mailtoLink } createGiftUrl={ props.actions.createGiftUrl }/> </fieldset> diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index b5dd9184c..c4ab22cd8 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -3,10 +3,10 @@ import { withActions } from '@financial-times/x-interaction'; import Loading from './Loading'; import Form from './Form'; -import getGiftUrl from './lib/get-gift-url'; -import fetchGiftCredit from './lib/fetch-gift-credit'; +import api from './lib/api'; +import getNextAllowanceDate from './lib/get-next-allowance-date' -let hasAttempetedToFetchCredit = false; +let hasAttempetedToGetAllowance = false; let propsComposer; const withGiftFormActions = withActions({ @@ -19,16 +19,22 @@ const withGiftFormActions = withActions({ }, createGiftUrl() { - return getGiftUrl() + return api.createGiftUrl() .then(url => { return propsComposer.setGiftUrl(url); }) }, - fetchCredit() { - return fetchGiftCredit() - .then(credit => { - return { credit }; + getAllowance() { + return api.getGiftArticleAllowance() + .then(({ credit, monthlyAllowance }) => { + let dateText = undefined; + if (credit === 0) { + const nextAllowanceDate = getNextAllowanceDate(); + dateText = `${nextAllowanceDate.monthName} ${nextAllowanceDate.day}`; + } + + return { monthlyAllowance, credit, dateText }; }) .catch(() => { // do something @@ -41,9 +47,9 @@ const BaseTemplate = (props) => { propsComposer = props.composer; } - if (!hasAttempetedToFetchCredit && !props.isFreeArticle) { - hasAttempetedToFetchCredit = true; - props.actions.fetchCredit(); + if (!hasAttempetedToGetAllowance && !props.isFreeArticle) { + hasAttempetedToGetAllowance = true; + props.actions.getAllowance(); } return props.isLoading ? <Loading/> : <Form {...props}/>; diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index beafb82e7..8f50ef278 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -4,16 +4,25 @@ import styles from './GiftArticle.css'; const messageClassName = styles.message; const boldTextClassName = styles.bold; -export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit }) => { +export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowance, dateText }) => { if (isFreeArticle) { return (<div className={ messageClassName }>This article is currently <span className={ boldTextClassName }>free</span> for anyone to read</div>); } if (isGift) { + if (credit === 0) { + return ( + <div className={ messageClassName }>You’ve used all your <span className={ boldTextClassName }>gift articles</span><br /> + You’ll get your next { monthlyAllowance } on <span className={ boldTextClassName }>{ dateText }</span>< + /div> + ); + } + if (isGiftUrlCreated) { return (<div className={ messageClassName }>This link can be opened up to 3 times</div>); } + return (<div className={ messageClassName }>You have <span className={ boldTextClassName }>{ credit } gift articles</span> left this month</div>); } diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index dd69b8a42..6fe8355a7 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,22 +4,29 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, mailtoLink, createGiftUrl }) => ( +export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl }) => ( <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> - <Url + { credit === 0 && isGift ? null : <Url isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } url={ url } urlType={ urlType }/> + } + <Message isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } isFreeArticle={ isFreeArticle } - credit={ credit }/> - <Buttons + credit={ credit } + monthlyAllowance={ monthlyAllowance } + dateText={ dateText } + /> + + { credit === 0 && isGift ? null : <Buttons isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } mailtoLink={ mailtoLink } createGiftUrl={ createGiftUrl }/> + } </div> ); diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js new file mode 100644 index 000000000..f30be87ef --- /dev/null +++ b/components/x-gift-article/src/lib/api.js @@ -0,0 +1,12 @@ +const delay = ms => new Promise(r => setTimeout(r, ms)); + +module.exports = { + getGiftArticleAllowance: () => { + return delay(1000) + .then(() => ({ monthlyAllowance: 20, credit: 0 })) + }, + createGiftUrl: () => { + return delay(2000) + .then(() => ('https://gift-url')) + } +} diff --git a/components/x-gift-article/src/lib/fetch-gift-credit.js b/components/x-gift-article/src/lib/fetch-gift-credit.js deleted file mode 100644 index 07ee13d46..000000000 --- a/components/x-gift-article/src/lib/fetch-gift-credit.js +++ /dev/null @@ -1,6 +0,0 @@ -const delay = ms => new Promise(r => setTimeout(r, ms)); - -export default () => { - return delay(1000) - .then(() => (20)) -}; diff --git a/components/x-gift-article/src/lib/get-gift-url.js b/components/x-gift-article/src/lib/get-gift-url.js deleted file mode 100644 index 5aa24fa39..000000000 --- a/components/x-gift-article/src/lib/get-gift-url.js +++ /dev/null @@ -1,6 +0,0 @@ -const delay = ms => new Promise(r => setTimeout(r, ms)); - -export default () => { - return delay(2000) - .then(() => ('https://gift-url')) -}; diff --git a/components/x-gift-article/src/lib/get-next-allowance-date.js b/components/x-gift-article/src/lib/get-next-allowance-date.js new file mode 100644 index 000000000..8b067263e --- /dev/null +++ b/components/x-gift-article/src/lib/get-next-allowance-date.js @@ -0,0 +1,12 @@ +export default () => { + const now = new Date(); + const startOfNextMonth = new Date(Date.UTC(now.getFullYear(), now.getMonth() + 1, 1)); + const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + + return { + year: String(startOfNextMonth.getFullYear()), + month: String(startOfNextMonth.getMonth() + 1), + monthName: monthNames[startOfNextMonth.getMonth()], + day: String(startOfNextMonth.getDate()) + }; +} From 23b0bd1b996ed927de561a0e6d03754e900cbede Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 17:00:01 +0100 Subject: [PATCH 088/760] make article plural depends on credit --- components/x-gift-article/src/Message.jsx | 5 ++++- components/x-gift-article/src/lib/api.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 8f50ef278..a2e114ee3 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -23,7 +23,10 @@ export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowa return (<div className={ messageClassName }>This link can be opened up to 3 times</div>); } - return (<div className={ messageClassName }>You have <span className={ boldTextClassName }>{ credit } gift articles</span> left this month</div>); + return ( + <div className={ messageClassName }> + You have <span className={ boldTextClassName }>{ credit } gift { credit === 1 ? 'article' : 'articles' }</span> left this month + </div>); } return (<div className={ messageClassName }>This link can only be read by existing subscribers</div>); diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index f30be87ef..8d169dd73 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -3,7 +3,7 @@ const delay = ms => new Promise(r => setTimeout(r, ms)); module.exports = { getGiftArticleAllowance: () => { return delay(1000) - .then(() => ({ monthlyAllowance: 20, credit: 0 })) + .then(() => ({ monthlyAllowance: 20, credit: 1 })) }, createGiftUrl: () => { return delay(2000) From 5686bd8f65f142d9bbcbf21eb22b6a6b55fdf31d Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 23 Jul 2018 17:16:01 +0100 Subject: [PATCH 089/760] tidy up --- components/x-gift-article/src/GiftArticle.jsx | 9 +----- components/x-gift-article/src/lib/api.js | 2 +- .../x-gift-article/src/lib/props-composer.js | 29 +++++++++++++++---- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index c4ab22cd8..386182aac 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -4,7 +4,6 @@ import Loading from './Loading'; import Form from './Form'; import api from './lib/api'; -import getNextAllowanceDate from './lib/get-next-allowance-date' let hasAttempetedToGetAllowance = false; let propsComposer; @@ -28,13 +27,7 @@ const withGiftFormActions = withActions({ getAllowance() { return api.getGiftArticleAllowance() .then(({ credit, monthlyAllowance }) => { - let dateText = undefined; - if (credit === 0) { - const nextAllowanceDate = getNextAllowanceDate(); - dateText = `${nextAllowanceDate.monthName} ${nextAllowanceDate.day}`; - } - - return { monthlyAllowance, credit, dateText }; + return propsComposer.setAllowance(credit, monthlyAllowance); }) .catch(() => { // do something diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 8d169dd73..f30be87ef 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -3,7 +3,7 @@ const delay = ms => new Promise(r => setTimeout(r, ms)); module.exports = { getGiftArticleAllowance: () => { return delay(1000) - .then(() => ({ monthlyAllowance: 20, credit: 1 })) + .then(() => ({ monthlyAllowance: 20, credit: 0 })) }, createGiftUrl: () => { return delay(2000) diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 4c4deb8f9..cddf3cc29 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -1,4 +1,5 @@ import createMailtoLink from './create-mailto-link'; +import getNextAllowanceDate from './get-next-allowance-date'; export class GiftArticlePropsComposer { constructor({title, isFreeArticle, articleTitle, articleUrl}) { @@ -7,6 +8,8 @@ export class GiftArticlePropsComposer { this.articleTitle = articleTitle; this.articleUrl = articleUrl; this.isGiftUrlCreated = false; + this.credit = undefined; + this.monthlyAllowance = undefined; this.urls = { dummy: 'https://dummy-url', @@ -18,17 +21,17 @@ export class GiftArticlePropsComposer { dummy: 'example-gift-link', gift: 'gift-link', nonGift: 'non-gift-link' - } + }; this.mailtoLinks = { gift: undefined, nonGift: createMailtoLink(this.articleTitle, this.articleUrl) - } + }; this.types = { gift: 'giftLink', nonGift: 'nonGiftLink' - } + }; } getDefault() { @@ -51,7 +54,7 @@ export class GiftArticlePropsComposer { urlType: this.urls.gift ? this.urlTypes.gift : this.urlTypes.dummy, mailtoLink: this.mailtoLinks.gift, type: this.types.gift - } + }; } displayNonGiftUrlSection() { @@ -61,7 +64,7 @@ export class GiftArticlePropsComposer { urlType: this.urlTypes.nonGift, mailtoLink: this.mailtoLinks.nonGift, type: this.types.nonGift - } + }; } setGiftUrl(url) { @@ -74,6 +77,22 @@ export class GiftArticlePropsComposer { url: this.urls.gift, urlType: this.urlTypes.gift, mailtoLink: this.mailtoLinks.gift + }; + } + + setAllowance(credit, monthlyAllowance) { + let dateText = undefined; + this.credit = credit; + this.monthlyAllowance = monthlyAllowance; + if (credit === 0) { + const nextAllowanceDate = getNextAllowanceDate(); + dateText = `${nextAllowanceDate.monthName} ${nextAllowanceDate.day}`; } + + return { + credit: this.credit, + monthlyAllowance: this.monthlyAllowance, + dateText + }; } } From bcc8433a136c502f05ef02e8064632325294f079 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 24 Jul 2018 14:22:41 +0100 Subject: [PATCH 090/760] WIP --- components/x-gift-article/package.json | 2 +- components/x-gift-article/src/lib/api.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index a854be575..a51e37898 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -1,7 +1,7 @@ { "name": "@financial-times/x-gift-article", "version": "1.0.0", - "description": "This module provides templates for gift article form", + "description": "This module provides gift article form", "main": "dist/GiftArticleWrapper.cjs.js", "browser": "dist/GiftArticleWrapper.es5.js", "module": "dist/GiftArticleWrapper.esm.js", diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index f30be87ef..88aef1f1f 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -2,8 +2,11 @@ const delay = ms => new Promise(r => setTimeout(r, ms)); module.exports = { getGiftArticleAllowance: () => { - return delay(1000) - .then(() => ({ monthlyAllowance: 20, credit: 0 })) + return fetch('/article-email/credits', { credentials: 'same-origin' }) + .then(response => response.json()) + .then(json => { + return { monthlyAllowance: json.credits.allowance, credit: json.credits.remainingCredits }; + }); }, createGiftUrl: () => { return delay(2000) From 4ff7b4f8ff02b3cfbf7720dae7f91cb4e68f2e6d Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Tue, 24 Jul 2018 15:36:50 +0100 Subject: [PATCH 091/760] =?UTF-8?q?Try=20mocking=20some=20fetch=20requests?= =?UTF-8?q?=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/stories/free-article.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index a603e7d10..e3be8a2b7 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -14,6 +14,15 @@ exports.knobs = [ 'articleTitle' ]; +exports.fetchMock = fetchMock => { + fetchMock + .restore() + .get( + '__myft/api', + { 'some': 'response' }, + ); +}; + // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; From 04d28613e93a32ac959f5a226350588b36d3cee7 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 24 Jul 2018 16:08:25 +0100 Subject: [PATCH 092/760] mock credit response --- .../x-gift-article/stories/free-article.js | 9 ----- components/x-gift-article/stories/index.js | 1 + .../stories/with-gift-credits.js | 15 ++++++++ .../stories/without-gift-credits.js | 34 +++++++++++++++++++ 4 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 components/x-gift-article/stories/without-gift-credits.js diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index e3be8a2b7..a603e7d10 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -14,15 +14,6 @@ exports.knobs = [ 'articleTitle' ]; -exports.fetchMock = fetchMock => { - fetchMock - .restore() - .get( - '__myft/api', - { 'some': 'response' }, - ); -}; - // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 602cdcdc6..b7e4b1f82 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -10,6 +10,7 @@ exports.dependencies = { }; exports.stories = [ require('./with-gift-credits'), + require('./without-gift-credits'), require('./free-article') ]; exports.knobs = require('./knobs'); diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 94a231be6..38801c870 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -17,3 +17,18 @@ exports.knobs = [ // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; + +exports.fetchMock = fetchMock => { + fetchMock + .get( + '/article-email/credits', + { 'credits': + { + 'allowance': 20, + 'consumedCredits': 19, + 'remainingCredits': 1, + 'renewalDate': '2018-08-01T00:00:00Z' + } + }, + ); +}; diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js new file mode 100644 index 000000000..4c62e92d7 --- /dev/null +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -0,0 +1,34 @@ +exports.title = 'Without gift credits'; + +exports.data = { + title: 'Share this article (without credit)', + isFreeArticle: false, + articleUrl: 'https://www.ft.com/content/blahblah', + articleTitle: 'Title Title Title Title', +}; + +exports.knobs = [ + 'title', + 'isFreeArticle', + 'articleUrl', + 'articleTitle' +]; + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module; + +exports.fetchMock = fetchMock => { + fetchMock + .get( + '/article-email/credits', + { 'credits': + { + 'allowance': 20, + 'consumedCredits': 20, + 'remainingCredits': 0, + 'renewalDate': '2018-08-01T00:00:00Z' + } + }, + ); +}; From c55570e02e4d0bc9d1d0430192889aa2ebefc4f0 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 25 Jul 2018 17:02:55 +0100 Subject: [PATCH 093/760] mock fetch gift link --- components/x-gift-article/src/Form.jsx | 3 ++- components/x-gift-article/src/GiftArticle.jsx | 10 ++++---- components/x-gift-article/src/Message.jsx | 4 +-- components/x-gift-article/src/UrlSection.jsx | 3 ++- components/x-gift-article/src/lib/api.js | 25 ++++++++++++++++--- .../x-gift-article/src/lib/props-composer.js | 13 +++++++--- .../stories/with-gift-credits.js | 15 ++++++++++- 7 files changed, 55 insertions(+), 18 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 18287ff0b..11c6b1b6f 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -29,7 +29,8 @@ export default (props) => ( monthlyAllowance={ props.monthlyAllowance } dateText={ props.dateText } mailtoLink={ props.mailtoLink } - createGiftUrl={ props.actions.createGiftUrl }/> + createGiftUrl={ props.actions.createGiftUrl } + redemptionLimit={ props.redemptionLimit }/> </fieldset> </form> ); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 386182aac..d9987e3c5 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -8,7 +8,7 @@ import api from './lib/api'; let hasAttempetedToGetAllowance = false; let propsComposer; -const withGiftFormActions = withActions({ +const withGiftFormActions = withActions(({ articleId, sessionId }) => ({ displayGiftUrlSection() { return propsComposer.displayGiftUrlSection(); }, @@ -18,9 +18,9 @@ const withGiftFormActions = withActions({ }, createGiftUrl() { - return api.createGiftUrl() - .then(url => { - return propsComposer.setGiftUrl(url); + return api.createGiftUrl(articleId, sessionId) + .then(({ redemptionUrl, redemptionLimit }) => { + return propsComposer.setGiftUrl(redemptionUrl, redemptionLimit); }) }, @@ -33,7 +33,7 @@ const withGiftFormActions = withActions({ // do something }) } -}); +})); const BaseTemplate = (props) => { if (!propsComposer) { diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index a2e114ee3..2334407a8 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -4,7 +4,7 @@ import styles from './GiftArticle.css'; const messageClassName = styles.message; const boldTextClassName = styles.bold; -export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowance, dateText }) => { +export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowance, dateText, redemptionLimit }) => { if (isFreeArticle) { return (<div className={ messageClassName }>This article is currently <span className={ boldTextClassName }>free</span> for anyone to read</div>); @@ -20,7 +20,7 @@ export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowa } if (isGiftUrlCreated) { - return (<div className={ messageClassName }>This link can be opened up to 3 times</div>); + return (<div className={ messageClassName }>This link can be opened up to { redemptionLimit } times</div>); } return ( diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 6fe8355a7..7e5ff78a7 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,7 +4,7 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl }) => ( +export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl, redemptionLimit }) => ( <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> { credit === 0 && isGift ? null : <Url isGift={ isGift } @@ -20,6 +20,7 @@ export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, c credit={ credit } monthlyAllowance={ monthlyAllowance } dateText={ dateText } + redemptionLimit={ redemptionLimit } /> { credit === 0 && isGift ? null : <Buttons diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 88aef1f1f..0f59da731 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -1,4 +1,4 @@ -const delay = ms => new Promise(r => setTimeout(r, ms)); +const REDEMPTION_LIMIT = 3; module.exports = { getGiftArticleAllowance: () => { @@ -8,8 +8,25 @@ module.exports = { return { monthlyAllowance: json.credits.allowance, credit: json.credits.remainingCredits }; }); }, - createGiftUrl: () => { - return delay(2000) - .then(() => ('https://gift-url')) + createGiftUrl: (articleId, sessionId) => { + return fetch('/article-email/gift-link', { + credentials: 'same-origin', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + contentUUID: articleId, + ftSessionSecure: sessionId + }) + }) + .then(response => response.ok ? response.json() : {}) + .then(body => { + if (body.errors) { + throw new Error(`Failed to get gift article link: ${body.errors.join(', ')}`); + } + + return Object.assign({}, body, { redemptionLimit: REDEMPTION_LIMIT }); + }); } } diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index cddf3cc29..fbfc31d89 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -2,11 +2,13 @@ import createMailtoLink from './create-mailto-link'; import getNextAllowanceDate from './get-next-allowance-date'; export class GiftArticlePropsComposer { - constructor({title, isFreeArticle, articleTitle, articleUrl}) { + constructor({title, isFreeArticle, articleTitle, articleUrl, articleId, sessionId}) { this.title = title || 'Share this article'; this.isFreeArticle = isFreeArticle; this.articleTitle = articleTitle; this.articleUrl = articleUrl; + this.articleId = articleId; + this.sessionId = sessionId; this.isGiftUrlCreated = false; this.credit = undefined; this.monthlyAllowance = undefined; @@ -43,7 +45,9 @@ export class GiftArticlePropsComposer { urlType: this.isFreeArticle ? this.urlTypes.nonGift : this.urlTypes.dummy, mailtoLink: this.isFreeArticle ? this.mailtoLinks.nonGift : undefined, isGiftUrlCreated: this.isGiftUrlCreated, - type: this.isFreeArticle ? this.types.nonGift : this.types.gift + type: this.isFreeArticle ? this.types.nonGift : this.types.gift, + articleId: this.articleId, + sessionId: this.sessionId }; } @@ -67,7 +71,7 @@ export class GiftArticlePropsComposer { }; } - setGiftUrl(url) { + setGiftUrl(url, limit) { this.urls.gift = url; this.isGiftUrlCreated = true; this.mailtoLinks.gift = createMailtoLink(this.articleTitle, url); @@ -76,7 +80,8 @@ export class GiftArticlePropsComposer { isGiftUrlCreated: this.isGiftUrlCreated, url: this.urls.gift, urlType: this.urlTypes.gift, - mailtoLink: this.mailtoLinks.gift + mailtoLink: this.mailtoLinks.gift, + redemptionLimit: limit }; } diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 38801c870..08913d83f 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -5,13 +5,17 @@ exports.data = { isFreeArticle: false, articleUrl: 'https://www.ft.com/content/blahblah', articleTitle: 'Title Title Title Title', + articleId: 'article id', + sessionId: 'session id' }; exports.knobs = [ 'title', 'isFreeArticle', 'articleUrl', - 'articleTitle' + 'articleTitle', + 'articleId', + 'sessionId' ]; // This reference is only required for hot module loading in development @@ -31,4 +35,13 @@ exports.fetchMock = fetchMock => { } }, ); + + fetchMock + .post( + '/article-email/gift-link', + { + 'redemptionUrl': 'https://giftarticle.ft.com/giftarticle/actions/redeem/00000', + 'remainingAllowance': 2 + }, + ); }; From 3f051f245f0b601403750cd2ee5cfed31d87dd15 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 26 Jul 2018 13:33:56 +0100 Subject: [PATCH 094/760] add mobile share buttons --- components/x-gift-article/package.json | 9 ++- components/x-gift-article/src/Form.jsx | 13 ++-- components/x-gift-article/src/GiftArticle.css | 7 ++- .../src/MobileShareButtons.scss | 63 +++++++++++++++++++ .../x-gift-article/src/MobileSharebuttons.jsx | 63 +++++++++++++++++++ .../x-gift-article/src/lib/props-composer.js | 14 ++++- components/x-gift-article/stories/index.js | 3 +- .../stories/with-gift-credits.js | 6 +- 8 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 components/x-gift-article/src/MobileShareButtons.scss create mode 100644 components/x-gift-article/src/MobileSharebuttons.jsx diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index a51e37898..74045ebd3 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -16,9 +16,12 @@ "license": "ISC", "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-rollup": "file:../../packages/x-rollup", "@financial-times/x-interaction": "file:../x-interaction", - "rollup": "^0.57.1", - "classnames": "^2.2.6" + "@financial-times/x-rollup": "file:../../packages/x-rollup", + "classnames": "^2.2.6", + "rollup": "^0.57.1" + }, + "devDependencies": { + "node-sass": "^4.9.2" } } diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 11c6b1b6f..54447af19 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -2,16 +2,17 @@ import { h } from '@financial-times/x-engine'; import Title from './Title'; import RadioButtonsSection from './RadioButtonsSection'; import UrlSection from './UrlSection'; - +import MobileShareButtons from './MobileShareButtons'; import styles from './GiftArticle.css'; -const containerClassNames = [ + +const formClassNames = [ 'o-forms', - styles.container + styles.form ].join(' '); export default (props) => ( - <form name="gift-form"> - <fieldset className={ containerClassNames }> + <form name="gift-form" className={ styles.container }> + <fieldset className={ formClassNames }> <Title title={ props.title }/> { props.isFreeArticle ? null : <RadioButtonsSection isGift={ props.isGift } @@ -32,5 +33,7 @@ export default (props) => ( createGiftUrl={ props.actions.createGiftUrl } redemptionLimit={ props.redemptionLimit }/> </fieldset> + + { props.showShareButtons ? <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> : null } </form> ); diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css index fd654a989..76f1a0e11 100644 --- a/components/x-gift-article/src/GiftArticle.css +++ b/components/x-gift-article/src/GiftArticle.css @@ -3,6 +3,11 @@ max-width: 500px; } +.form { + max-width: none; + padding: 0; +} + .bold { font-weight: 600; } @@ -25,7 +30,7 @@ .title { font-size: 20px; line-height: 24px; - margin-bottom: 24px; + margin-bottom: 20px; } .url { diff --git a/components/x-gift-article/src/MobileShareButtons.scss b/components/x-gift-article/src/MobileShareButtons.scss new file mode 100644 index 000000000..3c251bd79 --- /dev/null +++ b/components/x-gift-article/src/MobileShareButtons.scss @@ -0,0 +1,63 @@ +// @mixin socialColor($background-color) { +// @if $background-color == null { +// background-color: transparent; +// } @else if map-has-key($o-share-colors, $background-color) { +// background-color: #{map-get($o-share-colors, $background-color)}; +// } @else { +// background-color: oColorsGetPaletteColor($background-color); +// } +// } + +@mixin shareButton($social-media-name, $background-color) { + &, + &:hover, + &:active:not([disabled]), + &:not([disabled]):hover, + &:focus { + width: 100%; + color: #fff; + border: 1px solid #fff !important; + display: flex; + align-items: center; + justify-content: center; + text-decoration: none; + &:before { + position: absolute; + left: 0; + top: 0; + } + + background-color: $background-color; + // @include socialColor($social-media-name); + } +} + +.container { + margin-top: 16px; + width: 100%; + ul { + padding-left: 0; + margin-left: -10px; + } +} + +.button { + width: calc(50% - 10px); + margin: 10px 0 0 10px; +} + +.facebook { + @include shareButton('facebook', #3b579d); +} + +.twitter { + @include shareButton('twitter', #1da1f2); +} + +.linkedin { + @include shareButton('linkedin', #0077b5); +} + +.whatsapp { + @include shareButton('whatsapp', #25d366); +} diff --git a/components/x-gift-article/src/MobileSharebuttons.jsx b/components/x-gift-article/src/MobileSharebuttons.jsx new file mode 100644 index 000000000..00f8c11ea --- /dev/null +++ b/components/x-gift-article/src/MobileSharebuttons.jsx @@ -0,0 +1,63 @@ +import { h } from '@financial-times/x-engine'; +import Title from './Title'; + +import styles from './MobileShareButtons.scss'; +const containerClassNames = [ + 'o-share', + 'o-share--inverse', + styles.container +].join(' '); + +const buttonClassNames = [ + 'o-share__action', + styles.button +].join(' '); + +const whatsappButtonClassNames = [ + buttonClassNames, + 'o-share__action--whatsapp' +].join(' '); + +const facebookClassNames = [ + 'o-share__icon', + 'o-share__icon--facebook', + styles.facebook +].join(' '); + +const twitterClassNames = [ + 'o-share__icon', + 'o-share__icon--twitter', + styles.twitter +].join(' '); + +const linkedinClassNames = [ + 'o-share__icon', + 'o-share__icon--linkedin', + styles.linkedin +].join(' '); + +const whatsappClassNames = [ + 'o-share__icon', + 'o-share__icon--whatsapp', + styles.whatsapp +].join(' '); + +export default ({ mobileShareLinks }) => ( + <div className={ containerClassNames } data-o-component="o-share"> + <Title title={ 'Share on Social' }/> + <ul> + <li className={ buttonClassNames } data-share="facebook"> + <a className={ facebookClassNames } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook">Facebook<span className="o-share__text">(opens new window)</span></a> + </li> + <li className={ buttonClassNames } data-share="twitter"> + <a className={ twitterClassNames } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter">Twitter<span className="o-share__text">(opens new window)</span></a> + </li> + <li className={ buttonClassNames } data-share="linkedin"> + <a className={ linkedinClassNames } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin">LinkedIn<span className="o-share__text">(opens new window)</span></a> + </li> + <li className={ whatsappButtonClassNames } data-share="whatsapp"> + <a className={ whatsappClassNames } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp">Whatsapp<span className="o-share__text">(opens new window)</span></a> + </li> + </ul> + </div> +); diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index fbfc31d89..d02adc550 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -2,7 +2,7 @@ import createMailtoLink from './create-mailto-link'; import getNextAllowanceDate from './get-next-allowance-date'; export class GiftArticlePropsComposer { - constructor({title, isFreeArticle, articleTitle, articleUrl, articleId, sessionId}) { + constructor({ title, isFreeArticle, articleTitle, articleUrl, articleId, sessionId, showMobileShareLinks }) { this.title = title || 'Share this article'; this.isFreeArticle = isFreeArticle; this.articleTitle = articleTitle; @@ -12,6 +12,7 @@ export class GiftArticlePropsComposer { this.isGiftUrlCreated = false; this.credit = undefined; this.monthlyAllowance = undefined; + this.showMobileShareLinks = showMobileShareLinks; this.urls = { dummy: 'https://dummy-url', @@ -34,6 +35,13 @@ export class GiftArticlePropsComposer { gift: 'giftLink', nonGift: 'nonGiftLink' }; + + this.mobileShareLinks = showMobileShareLinks ? { + facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(articleUrl)}&t=${encodeURIComponent(articleTitle)}`, + twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(articleTitle)}&via=financialtimes`, + linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(articleUrl)}&title=${encodeURIComponent(articleTitle)}&source=Financial+Times`, + whatsapp: `whatsapp://send?text=${encodeURIComponent(articleTitle)}%20-%20${encodeURIComponent(articleUrl)}` + } : undefined; } getDefault() { @@ -47,7 +55,9 @@ export class GiftArticlePropsComposer { isGiftUrlCreated: this.isGiftUrlCreated, type: this.isFreeArticle ? this.types.nonGift : this.types.gift, articleId: this.articleId, - sessionId: this.sessionId + sessionId: this.sessionId, + showShareButtons: this.showMobileShareLinks, + mobileShareLinks: this.mobileShareLinks }; } diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index b7e4b1f82..3b97b2c9f 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -6,7 +6,8 @@ exports.dependencies = { 'o-fonts': '^3.0.0', 'o-buttons': '^5.13.1', 'o-forms': '^5.7.3', - 'o-loading': '^2.2.2' + 'o-loading': '^2.2.2', + 'o-share': '^6.2.0' }; exports.stories = [ require('./with-gift-credits'), diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 08913d83f..f40d5f051 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -6,7 +6,8 @@ exports.data = { articleUrl: 'https://www.ft.com/content/blahblah', articleTitle: 'Title Title Title Title', articleId: 'article id', - sessionId: 'session id' + sessionId: 'session id', + showMobileShareLinks: true }; exports.knobs = [ @@ -15,7 +16,8 @@ exports.knobs = [ 'articleUrl', 'articleTitle', 'articleId', - 'sessionId' + 'sessionId', + 'showMobileShareLinks' ]; // This reference is only required for hot module loading in development From 950ea7f3fa1e02b231ab5f82616279f3f32b40c0 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 27 Jul 2018 10:58:59 +0100 Subject: [PATCH 095/760] tidy up --- components/x-gift-article/src/Form.jsx | 15 +++++++--- components/x-gift-article/src/GiftArticle.jsx | 2 +- components/x-gift-article/src/Message.jsx | 25 +++++++++++++---- .../x-gift-article/src/MobileSharebuttons.jsx | 16 ++++++++--- .../src/RadioButtonsSection.jsx | 28 ++++++++++++++++--- components/x-gift-article/src/Url.jsx | 10 ++++++- components/x-gift-article/src/UrlSection.jsx | 18 ++++++------ components/x-gift-article/src/lib/api.js | 2 +- .../x-gift-article/src/lib/props-composer.js | 2 +- 9 files changed, 88 insertions(+), 30 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 54447af19..4b9703da9 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -13,12 +13,15 @@ const formClassNames = [ export default (props) => ( <form name="gift-form" className={ styles.container }> <fieldset className={ formClassNames }> + <Title title={ props.title }/> + { props.isFreeArticle ? null : <RadioButtonsSection - isGift={ props.isGift } - displayGiftUrlSection={ props.actions.displayGiftUrlSection } - displayNonGiftUrlSection={ props.actions.displayNonGiftUrlSection }/> + isGift={ props.isGift } + displayGiftUrlSection={ props.actions.displayGiftUrlSection } + displayNonGiftUrlSection={ props.actions.displayNonGiftUrlSection }/> } + <UrlSection type={ props.type } isGift={ props.isGift } @@ -32,8 +35,12 @@ export default (props) => ( mailtoLink={ props.mailtoLink } createGiftUrl={ props.actions.createGiftUrl } redemptionLimit={ props.redemptionLimit }/> + </fieldset> - { props.showShareButtons ? <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> : null } + { props.showShareButtons ? + <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> : null + } + </form> ); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index d9987e3c5..3bd9862a1 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -18,7 +18,7 @@ const withGiftFormActions = withActions(({ articleId, sessionId }) => ({ }, createGiftUrl() { - return api.createGiftUrl(articleId, sessionId) + return api.getGiftUrl(articleId, sessionId) .then(({ redemptionUrl, redemptionLimit }) => { return propsComposer.setGiftUrl(redemptionUrl, redemptionLimit); }) diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 2334407a8..cea500028 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -7,20 +7,29 @@ const boldTextClassName = styles.bold; export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowance, dateText, redemptionLimit }) => { if (isFreeArticle) { - return (<div className={ messageClassName }>This article is currently <span className={ boldTextClassName }>free</span> for anyone to read</div>); + return ( + <div className={ messageClassName }> + This article is currently <span className={ boldTextClassName }>free</span> for anyone to read + </div> + ); } if (isGift) { if (credit === 0) { return ( - <div className={ messageClassName }>You’ve used all your <span className={ boldTextClassName }>gift articles</span><br /> - You’ll get your next { monthlyAllowance } on <span className={ boldTextClassName }>{ dateText }</span>< - /div> + <div className={ messageClassName }> + You’ve used all your <span className={ boldTextClassName }>gift articles</span><br /> + You’ll get your next { monthlyAllowance } on <span className={ boldTextClassName }>{ dateText }</span> + </div> ); } if (isGiftUrlCreated) { - return (<div className={ messageClassName }>This link can be opened up to { redemptionLimit } times</div>); + return ( + <div className={ messageClassName }> + This link can be opened up to { redemptionLimit } times + </div> + ); } return ( @@ -29,6 +38,10 @@ export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowa </div>); } - return (<div className={ messageClassName }>This link can only be read by existing subscribers</div>); + return ( + <div className={ messageClassName }> + This link can only be read by existing subscribers + </div> + ); }; diff --git a/components/x-gift-article/src/MobileSharebuttons.jsx b/components/x-gift-article/src/MobileSharebuttons.jsx index 00f8c11ea..8b54b019b 100644 --- a/components/x-gift-article/src/MobileSharebuttons.jsx +++ b/components/x-gift-article/src/MobileSharebuttons.jsx @@ -47,16 +47,24 @@ export default ({ mobileShareLinks }) => ( <Title title={ 'Share on Social' }/> <ul> <li className={ buttonClassNames } data-share="facebook"> - <a className={ facebookClassNames } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook">Facebook<span className="o-share__text">(opens new window)</span></a> + <a className={ facebookClassNames } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook"> + Facebook<span className="o-share__text">(opens new window)</span> + </a> </li> <li className={ buttonClassNames } data-share="twitter"> - <a className={ twitterClassNames } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter">Twitter<span className="o-share__text">(opens new window)</span></a> + <a className={ twitterClassNames } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter"> + Twitter<span className="o-share__text">(opens new window)</span> + </a> </li> <li className={ buttonClassNames } data-share="linkedin"> - <a className={ linkedinClassNames } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin">LinkedIn<span className="o-share__text">(opens new window)</span></a> + <a className={ linkedinClassNames } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin"> + LinkedIn<span className="o-share__text">(opens new window)</span> + </a> </li> <li className={ whatsappButtonClassNames } data-share="whatsapp"> - <a className={ whatsappClassNames } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp">Whatsapp<span className="o-share__text">(opens new window)</span></a> + <a className={ whatsappClassNames } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp"> + Whatsapp<span className="o-share__text">(opens new window)</span> + </a> </li> </ul> </div> diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index e95102e92..855f32521 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -11,11 +11,31 @@ const radioSectionClassNames = [ export default ({ isGift, displayGiftUrlSection, displayNonGiftUrlSection }) => ( <div className={ radioSectionClassNames }> - <input type="radio" name="gift-form__radio" value="giftLink" className="o-forms__radio" id="giftLink" checked={ isGift } onChange={ displayGiftUrlSection }/> - <label htmlFor="giftLink" className="o-forms__label">with <span className={ boldTextClassName }>anyone</span></label> + <input + type="radio" + name="gift-form__radio" + value="giftLink" + className="o-forms__radio" + id="giftLink" + checked={ isGift } + onChange={ displayGiftUrlSection }/> - <input type="radio" name="gift-form__radio" value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" checked={ !isGift } onChange={ displayNonGiftUrlSection }/> - <label htmlFor="nonGiftLink" className="o-forms__label">with <span className={ boldTextClassName }>other subscribers</span></label> + <label htmlFor="giftLink" className="o-forms__label"> + with <span className={ boldTextClassName }>anyone</span> + </label> + + <input + type="radio" + name="gift-form__radio" + value="nonGiftLink" + className="o-forms__radio" + id="nonGiftLink" + checked={ !isGift } + onChange={ displayNonGiftUrlSection }/> + + <label htmlFor="nonGiftLink" className="o-forms__label"> + with <span className={ boldTextClassName }>other subscribers</span> + </label> </div> ); diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 5ad46b792..58ca81cc8 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -7,5 +7,13 @@ const urlClassNames = [ ].join(' '); export default ({ isGift, isGiftUrlCreated, url, urlType }) => { - return (<input type="text" name={ urlType } value={ url } className={ urlClassNames } disabled={ isGift && !isGiftUrlCreated } readOnly></input>); + return ( + <input + type="text" + name={ urlType } + value={ url } + className={ urlClassNames } + disabled={ isGift && !isGiftUrlCreated } + readOnly/> + ); }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 7e5ff78a7..5d18e6814 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -6,11 +6,12 @@ import styles from './GiftArticle.css'; export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl, redemptionLimit }) => ( <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> + { credit === 0 && isGift ? null : <Url - isGift={ isGift } - isGiftUrlCreated={ isGiftUrlCreated } - url={ url } - urlType={ urlType }/> + isGift={ isGift } + isGiftUrlCreated={ isGiftUrlCreated } + url={ url } + urlType={ urlType }/> } <Message @@ -24,10 +25,11 @@ export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, c /> { credit === 0 && isGift ? null : <Buttons - isGift={ isGift } - isGiftUrlCreated={ isGiftUrlCreated } - mailtoLink={ mailtoLink } - createGiftUrl={ createGiftUrl }/> + isGift={ isGift } + isGiftUrlCreated={ isGiftUrlCreated } + mailtoLink={ mailtoLink } + createGiftUrl={ createGiftUrl }/> } + </div> ); diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 0f59da731..69fa7c07a 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -8,7 +8,7 @@ module.exports = { return { monthlyAllowance: json.credits.allowance, credit: json.credits.remainingCredits }; }); }, - createGiftUrl: (articleId, sessionId) => { + getGiftUrl: (articleId, sessionId) => { return fetch('/article-email/gift-link', { credentials: 'same-origin', method: 'POST', diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index d02adc550..b05662ec2 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -15,7 +15,7 @@ export class GiftArticlePropsComposer { this.showMobileShareLinks = showMobileShareLinks; this.urls = { - dummy: 'https://dummy-url', + dummy: 'https://on.ft.com/gift_link', gift: undefined, nonGift: 'https://non-gift-url' }; From 999154e73c89444fe9a7b0557a1606b66b355f4e Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 27 Jul 2018 13:31:50 +0100 Subject: [PATCH 096/760] shorten urls --- components/x-gift-article/src/GiftArticle.jsx | 20 ++++++++++++++++--- components/x-gift-article/src/lib/api.js | 19 +++++++++++++++++- .../x-gift-article/src/lib/props-composer.js | 13 ++++++++++-- .../stories/with-gift-credits.js | 16 +++++++++++---- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 3bd9862a1..cc8248dad 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -8,19 +8,32 @@ import api from './lib/api'; let hasAttempetedToGetAllowance = false; let propsComposer; -const withGiftFormActions = withActions(({ articleId, sessionId }) => ({ +const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) => ({ displayGiftUrlSection() { return propsComposer.displayGiftUrlSection(); }, displayNonGiftUrlSection() { - return propsComposer.displayNonGiftUrlSection(); + if (propsComposer.isNonGiftUrlShortened) { + return propsComposer.displayNonGiftUrlSection(); + } else { + return api.getShorterUrl(articleUrl) + .then(({ url, isShortened }) => { + if (isShortened) { + propsComposer.setShortenedNonGiftUrl(url); + } + return propsComposer.displayNonGiftUrlSection(); + }) + } }, createGiftUrl() { return api.getGiftUrl(articleId, sessionId) .then(({ redemptionUrl, redemptionLimit }) => { - return propsComposer.setGiftUrl(redemptionUrl, redemptionLimit); + return api.getShorterUrl(redemptionUrl) + .then(({ url, isShortened }) => { + return propsComposer.setGiftUrl(url, redemptionLimit, isShortened); + }) }) }, @@ -33,6 +46,7 @@ const withGiftFormActions = withActions(({ articleId, sessionId }) => ({ // do something }) } + })); const BaseTemplate = (props) => { diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 69fa7c07a..ffbccaa1a 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -28,5 +28,22 @@ module.exports = { return Object.assign({}, body, { redemptionLimit: REDEMPTION_LIMIT }); }); - } + }, + getShorterUrl: (originalUrl) => { + let url = originalUrl; + let isShortened = false; + + return fetch('/article/shorten-url/' + encodeURIComponent(originalUrl), { credentials: 'same-origin' }) + .then(response => response.json()) + .then(json => { + if (json.shortenedUrl) { + isShortened = true; + url = json.shortenedUrl; + } + return { url, isShortened }; + }) + .catch(() => { + return { url, isShortened }; + }); + }, } diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index b05662ec2..0942f5e84 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -13,11 +13,13 @@ export class GiftArticlePropsComposer { this.credit = undefined; this.monthlyAllowance = undefined; this.showMobileShareLinks = showMobileShareLinks; + this.isGiftUrlShortened = false; + this.isNonGiftUrlShortened = false; this.urls = { dummy: 'https://on.ft.com/gift_link', gift: undefined, - nonGift: 'https://non-gift-url' + nonGift: articleUrl }; this.urlTypes = { @@ -55,6 +57,7 @@ export class GiftArticlePropsComposer { isGiftUrlCreated: this.isGiftUrlCreated, type: this.isFreeArticle ? this.types.nonGift : this.types.gift, articleId: this.articleId, + articleUrl: this.articleUrl, sessionId: this.sessionId, showShareButtons: this.showMobileShareLinks, mobileShareLinks: this.mobileShareLinks @@ -81,9 +84,10 @@ export class GiftArticlePropsComposer { }; } - setGiftUrl(url, limit) { + setGiftUrl(url, limit, isShortened) { this.urls.gift = url; this.isGiftUrlCreated = true; + this.isGiftUrlShortened = isShortened; this.mailtoLinks.gift = createMailtoLink(this.articleTitle, url); return { @@ -110,4 +114,9 @@ export class GiftArticlePropsComposer { dateText }; } + + setShortenedNonGiftUrl(shortenedUrl) { + this.isNonGiftUrlShortened = true; + this.urls.nonGift = shortenedUrl; + } } diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index f40d5f051..732eb6069 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -3,7 +3,7 @@ exports.title = 'With gift credits'; exports.data = { title: 'Share this article (with credit)', isFreeArticle: false, - articleUrl: 'https://www.ft.com/content/blahblah', + articleUrl: 'article-url', articleTitle: 'Title Title Title Title', articleId: 'article id', sessionId: 'session id', @@ -35,15 +35,23 @@ exports.fetchMock = fetchMock => { 'remainingCredits': 1, 'renewalDate': '2018-08-01T00:00:00Z' } - }, + } + ) + .get( + 'begin:/article/shorten-url/gift', + { shortenedUrl: 'https://shortened-gift-url' } + ) + .get( + 'begin:/article/shorten-url/article', + { shortenedUrl: 'https://shortened-non-gift-url' } ); fetchMock .post( '/article-email/gift-link', { - 'redemptionUrl': 'https://giftarticle.ft.com/giftarticle/actions/redeem/00000', + 'redemptionUrl': 'gift-url-redeemed', 'remainingAllowance': 2 - }, + } ); }; From 160d64f4d87599c72d8c61740f848e1899ae4270 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 27 Jul 2018 15:54:03 +0100 Subject: [PATCH 097/760] hide copy button if the browser doesn't support copy command --- components/x-gift-article/src/Buttons.jsx | 4 +-- components/x-gift-article/src/Form.jsx | 3 +- .../x-gift-article/src/GiftArticleWrapper.jsx | 3 +- components/x-gift-article/src/UrlSection.jsx | 5 ++-- .../x-gift-article/src/lib/props-composer.js | 30 ++++++++++--------- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 2c9f66817..17f5b676a 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -16,12 +16,12 @@ const ButtonWithGapClassNames = [ ].join(' '); -export default ({ isGift, isGiftUrlCreated, mailtoLink, createGiftUrl }) => { +export default ({ isGift, isGiftUrlCreated, mailtoLink, createGiftUrl, showCopyButton }) => { if (isGiftUrlCreated || !isGift) { return ( <div className={ ButtonsClassName }> - <button className={ ButtonWithGapClassNames } type="button">Copy link</button> + { showCopyButton ? <button className={ ButtonWithGapClassNames } type="button">Copy link</button> : null } <a className={ ButtonClassNames } href={ mailtoLink } target="_blank">Email link</a> </div> ); diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 4b9703da9..46411af18 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -34,7 +34,8 @@ export default (props) => ( dateText={ props.dateText } mailtoLink={ props.mailtoLink } createGiftUrl={ props.actions.createGiftUrl } - redemptionLimit={ props.redemptionLimit }/> + redemptionLimit={ props.redemptionLimit } + showCopyButton={ props.showCopyButton }/> </fieldset> diff --git a/components/x-gift-article/src/GiftArticleWrapper.jsx b/components/x-gift-article/src/GiftArticleWrapper.jsx index 6dcb051a3..25a189394 100644 --- a/components/x-gift-article/src/GiftArticleWrapper.jsx +++ b/components/x-gift-article/src/GiftArticleWrapper.jsx @@ -3,7 +3,8 @@ import { GiftArticlePropsComposer } from './lib/props-composer'; import { GiftArticle } from './GiftArticle'; const GiftArticleWrapper = (props) => { - const propsComposer = new GiftArticlePropsComposer(props); + const isCopySupported = document.queryCommandSupported && document.queryCommandSupported('copy'); + const propsComposer = new GiftArticlePropsComposer(props, isCopySupported); const composedProps = propsComposer.getDefault(); composedProps.composer = propsComposer; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 5d18e6814..8a615ca7b 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,7 +4,7 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl, redemptionLimit }) => ( +export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl, redemptionLimit, showCopyButton }) => ( <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> { credit === 0 && isGift ? null : <Url @@ -28,7 +28,8 @@ export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, c isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } mailtoLink={ mailtoLink } - createGiftUrl={ createGiftUrl }/> + createGiftUrl={ createGiftUrl } + showCopyButton={ showCopyButton }/> } </div> diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 0942f5e84..9bb60c63d 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -2,24 +2,25 @@ import createMailtoLink from './create-mailto-link'; import getNextAllowanceDate from './get-next-allowance-date'; export class GiftArticlePropsComposer { - constructor({ title, isFreeArticle, articleTitle, articleUrl, articleId, sessionId, showMobileShareLinks }) { - this.title = title || 'Share this article'; - this.isFreeArticle = isFreeArticle; - this.articleTitle = articleTitle; - this.articleUrl = articleUrl; - this.articleId = articleId; - this.sessionId = sessionId; + constructor(props, isCopySupported) { + this.title = props.title || 'Share this article'; + this.isFreeArticle = props.isFreeArticle; + this.articleTitle = props.articleTitle; + this.articleUrl = props.articleUrl; + this.articleId = props.articleId; + this.sessionId = props.sessionId; this.isGiftUrlCreated = false; this.credit = undefined; this.monthlyAllowance = undefined; - this.showMobileShareLinks = showMobileShareLinks; + this.showCopyButton = isCopySupported; + this.showMobileShareLinks = props.showMobileShareLinks; this.isGiftUrlShortened = false; this.isNonGiftUrlShortened = false; this.urls = { dummy: 'https://on.ft.com/gift_link', gift: undefined, - nonGift: articleUrl + nonGift: this.articleUrl }; this.urlTypes = { @@ -38,11 +39,11 @@ export class GiftArticlePropsComposer { nonGift: 'nonGiftLink' }; - this.mobileShareLinks = showMobileShareLinks ? { - facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(articleUrl)}&t=${encodeURIComponent(articleTitle)}`, - twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(articleTitle)}&via=financialtimes`, - linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(articleUrl)}&title=${encodeURIComponent(articleTitle)}&source=Financial+Times`, - whatsapp: `whatsapp://send?text=${encodeURIComponent(articleTitle)}%20-%20${encodeURIComponent(articleUrl)}` + this.mobileShareLinks = this.showMobileShareLinks ? { + facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(this.articleUrl)}&t=${encodeURIComponent(this.articleTitle)}`, + twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(this.articleUrl)}&text=${encodeURIComponent(this.articleTitle)}&via=financialtimes`, + linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(this.articleUrl)}&title=${encodeURIComponent(this.articleTitle)}&source=Financial+Times`, + whatsapp: `whatsapp://send?text=${encodeURIComponent(this.articleTitle)}%20-%20${encodeURIComponent(this.articleUrl)}` } : undefined; } @@ -59,6 +60,7 @@ export class GiftArticlePropsComposer { articleId: this.articleId, articleUrl: this.articleUrl, sessionId: this.sessionId, + showCopyButton: this.showCopyButton, showShareButtons: this.showMobileShareLinks, mobileShareLinks: this.mobileShareLinks }; From 4b8540b1db27bb35077a00007a9af0f99e958af9 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 27 Jul 2018 17:15:05 +0100 Subject: [PATCH 098/760] add copy actions --- components/x-gift-article/src/Buttons.jsx | 4 +- components/x-gift-article/src/Form.jsx | 2 + components/x-gift-article/src/GiftArticle.jsx | 11 +++++ components/x-gift-article/src/UrlSection.jsx | 4 +- .../src/lib/create-mailto-link.js | 6 --- .../x-gift-article/src/lib/props-composer.js | 2 +- .../src/lib/share-link-actions.js | 47 +++++++++++++++++++ 7 files changed, 66 insertions(+), 10 deletions(-) delete mode 100644 components/x-gift-article/src/lib/create-mailto-link.js create mode 100644 components/x-gift-article/src/lib/share-link-actions.js diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 17f5b676a..e35030d0a 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -16,12 +16,12 @@ const ButtonWithGapClassNames = [ ].join(' '); -export default ({ isGift, isGiftUrlCreated, mailtoLink, createGiftUrl, showCopyButton }) => { +export default ({ isGift, isGiftUrlCreated, mailtoLink, createGiftUrl, copyGiftUrl, copyNonGiftUrl, showCopyButton }) => { if (isGiftUrlCreated || !isGift) { return ( <div className={ ButtonsClassName }> - { showCopyButton ? <button className={ ButtonWithGapClassNames } type="button">Copy link</button> : null } + { showCopyButton ? <button className={ ButtonWithGapClassNames } type="button" onClick={ isGift ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> : null } <a className={ ButtonClassNames } href={ mailtoLink } target="_blank">Email link</a> </div> ); diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 46411af18..a0ed9ad1b 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -34,6 +34,8 @@ export default (props) => ( dateText={ props.dateText } mailtoLink={ props.mailtoLink } createGiftUrl={ props.actions.createGiftUrl } + copyGiftUrl={ props.actions.copyGiftUrl } + copyNonGiftUrl={ props.actions.copyNonGiftUrl } redemptionLimit={ props.redemptionLimit } showCopyButton={ props.showCopyButton }/> diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index cc8248dad..4d84a7a87 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -4,6 +4,7 @@ import Loading from './Loading'; import Form from './Form'; import api from './lib/api'; +import { copyToClipboard } from './lib/share-link-actions'; let hasAttempetedToGetAllowance = false; let propsComposer; @@ -45,6 +46,16 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = .catch(() => { // do something }) + }, + + copyGiftUrl() { + copyToClipboard(propsComposer.urls.gift); + return {}; + }, + + copyNonGiftUrl() { + copyToClipboard(propsComposer.urls.nonGift); + return {}; } })); diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 8a615ca7b..e3b80739c 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,7 +4,7 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl, redemptionLimit, showCopyButton }) => ( +export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl, copyGiftUrl, copyNonGiftUrl, redemptionLimit, showCopyButton }) => ( <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> { credit === 0 && isGift ? null : <Url @@ -29,6 +29,8 @@ export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, c isGiftUrlCreated={ isGiftUrlCreated } mailtoLink={ mailtoLink } createGiftUrl={ createGiftUrl } + copyGiftUrl={ copyGiftUrl } + copyNonGiftUrl={ copyNonGiftUrl } showCopyButton={ showCopyButton }/> } diff --git a/components/x-gift-article/src/lib/create-mailto-link.js b/components/x-gift-article/src/lib/create-mailto-link.js deleted file mode 100644 index ce3ec29f2..000000000 --- a/components/x-gift-article/src/lib/create-mailto-link.js +++ /dev/null @@ -1,6 +0,0 @@ -export default (articleTitle, shareUrl) => { - const subject = encodeURIComponent(articleTitle); - const body = encodeURIComponent(shareUrl); - - return `mailto:?subject=${subject}&body=${body}`; -} diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 9bb60c63d..1a08ed6f8 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -1,4 +1,4 @@ -import createMailtoLink from './create-mailto-link'; +import { createMailtoLink } from './share-link-actions'; import getNextAllowanceDate from './get-next-allowance-date'; export class GiftArticlePropsComposer { diff --git a/components/x-gift-article/src/lib/share-link-actions.js b/components/x-gift-article/src/lib/share-link-actions.js new file mode 100644 index 000000000..d4b5b34a8 --- /dev/null +++ b/components/x-gift-article/src/lib/share-link-actions.js @@ -0,0 +1,47 @@ +function createMailtoLink (articleTitle, shareUrl) { + const subject = encodeURIComponent(articleTitle); + const body = encodeURIComponent(shareUrl); + + return `mailto:?subject=${subject}&body=${body}`; +} + + +function copyToClipboard (url) { + const el = document.createElement('textarea'); + el.value = url; + el.setAttribute('readonly', ''); + el.style.position = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + // + // const oldContentEditable = inputEl.contentEditable; + // const oldReadOnly = inputEl.readOnly; + // const range = document.createRange(); + // + // inputEl.contenteditable = true; + // inputEl.readonly = false; + // inputEl.focus(); + // range.selectNodeContents(inputEl); + // + // const selection = window.getSelection(); + // + // try { + // selection.removeAllRanges(); + // selection.addRange(range); + // inputEl.setSelectionRange(0, 999999); + // } catch (err) { + // inputEl.select(); // IE11 etc. + // } + // inputEl.contentEditable = oldContentEditable; + // inputEl.readOnly = oldReadOnly; + // document.execCommand('copy'); + // inputEl.blur(); +} + +module.exports = { + createMailtoLink, + copyToClipboard +} From 613b7e261ab6ff4fb3201e135446bc8840c7e9d2 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 31 Jul 2018 13:56:29 +0100 Subject: [PATCH 099/760] add copy confirmation --- .../x-gift-article/src/CopyConfirmation.jsx | 17 +++++++++++++++++ components/x-gift-article/src/Form.jsx | 3 +++ components/x-gift-article/src/GiftArticle.jsx | 8 ++++++-- .../x-gift-article/src/lib/props-composer.js | 15 +++++++++++++-- components/x-gift-article/stories/index.js | 3 ++- 5 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 components/x-gift-article/src/CopyConfirmation.jsx diff --git a/components/x-gift-article/src/CopyConfirmation.jsx b/components/x-gift-article/src/CopyConfirmation.jsx new file mode 100644 index 000000000..4d23b5fcd --- /dev/null +++ b/components/x-gift-article/src/CopyConfirmation.jsx @@ -0,0 +1,17 @@ +import { h } from '@financial-times/x-engine'; + +export default ({ hideCopyConfirmation }) => ( + <div className="o-message o-message--alert-bleed o-message--success"> + <div className="o-message__container"> + + <div className="o-message__content"> + <p className="o-message__content-main"> + <span className="o-message__content-highlight">The link has been copied to your clipboard</span> + </p> + </div> + + <button className="o-message__close" aria-label="close" title="Close" onClick={ hideCopyConfirmation }></button> + + </div> + </div> +); diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index a0ed9ad1b..3798987ea 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -3,6 +3,7 @@ import Title from './Title'; import RadioButtonsSection from './RadioButtonsSection'; import UrlSection from './UrlSection'; import MobileShareButtons from './MobileShareButtons'; +import CopyConfirmation from './CopyConfirmation'; import styles from './GiftArticle.css'; const formClassNames = [ @@ -41,6 +42,8 @@ export default (props) => ( </fieldset> + { props.showCopyConfirmation ? <CopyConfirmation hideCopyConfirmation={ props.actions.hideCopyConfirmation }/> : null } + { props.showShareButtons ? <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> : null } diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 4d84a7a87..db4492b88 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -50,12 +50,16 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = copyGiftUrl() { copyToClipboard(propsComposer.urls.gift); - return {}; + return propsComposer.showCopyConfirmation(); }, copyNonGiftUrl() { copyToClipboard(propsComposer.urls.nonGift); - return {}; + return propsComposer.showCopyConfirmation(); + }, + + hideCopyConfirmation() { + return propsComposer.hideCopyConfirmation(); } })); diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 1a08ed6f8..ac77fef1a 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -61,6 +61,7 @@ export class GiftArticlePropsComposer { articleUrl: this.articleUrl, sessionId: this.sessionId, showCopyButton: this.showCopyButton, + showCopyConfirmation: false, showShareButtons: this.showMobileShareLinks, mobileShareLinks: this.mobileShareLinks }; @@ -72,7 +73,8 @@ export class GiftArticlePropsComposer { url: this.urls.gift || this.urls.dummy, urlType: this.urls.gift ? this.urlTypes.gift : this.urlTypes.dummy, mailtoLink: this.mailtoLinks.gift, - type: this.types.gift + type: this.types.gift, + showCopyConfirmation: false }; } @@ -82,7 +84,8 @@ export class GiftArticlePropsComposer { url: this.urls.nonGift, urlType: this.urlTypes.nonGift, mailtoLink: this.mailtoLinks.nonGift, - type: this.types.nonGift + type: this.types.nonGift, + showCopyConfirmation: false }; } @@ -121,4 +124,12 @@ export class GiftArticlePropsComposer { this.isNonGiftUrlShortened = true; this.urls.nonGift = shortenedUrl; } + + showCopyConfirmation() { + return { showCopyConfirmation: true }; + } + + hideCopyConfirmation() { + return { showCopyConfirmation: false }; + } } diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 3b97b2c9f..4cf9782ba 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -7,7 +7,8 @@ exports.dependencies = { 'o-buttons': '^5.13.1', 'o-forms': '^5.7.3', 'o-loading': '^2.2.2', - 'o-share': '^6.2.0' + 'o-share': '^6.2.0', + 'o-message': '^2.3.3' }; exports.stories = [ require('./with-gift-credits'), From f4661b5376333828e49349e22cc434a717c3c738 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 31 Jul 2018 14:59:16 +0100 Subject: [PATCH 100/760] modify copyToClipboard function --- .../src/lib/share-link-actions.js | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/components/x-gift-article/src/lib/share-link-actions.js b/components/x-gift-article/src/lib/share-link-actions.js index d4b5b34a8..ee6d87e52 100644 --- a/components/x-gift-article/src/lib/share-link-actions.js +++ b/components/x-gift-article/src/lib/share-link-actions.js @@ -6,39 +6,29 @@ function createMailtoLink (articleTitle, shareUrl) { } -function copyToClipboard (url) { - const el = document.createElement('textarea'); - el.value = url; - el.setAttribute('readonly', ''); +function copyToClipboard (copyText) { + + const selected = + document.getSelection().rangeCount > 0 // Check if there is any content selected previously + ? document.getSelection().getRangeAt(0) // Store selection if found + : false; // Mark as false to know no selection existed before + + const el = document.createElement('textarea'); // Create a <textarea> element + el.value = copyText; // Set its value to the string that you want copied + el.setAttribute('readonly', ''); // Make it readonly to be tamper-proof el.style.position = 'absolute'; - el.style.left = '-9999px'; - document.body.appendChild(el); - el.select(); - document.execCommand('copy'); - document.body.removeChild(el); - // - // const oldContentEditable = inputEl.contentEditable; - // const oldReadOnly = inputEl.readOnly; - // const range = document.createRange(); - // - // inputEl.contenteditable = true; - // inputEl.readonly = false; - // inputEl.focus(); - // range.selectNodeContents(inputEl); - // - // const selection = window.getSelection(); - // - // try { - // selection.removeAllRanges(); - // selection.addRange(range); - // inputEl.setSelectionRange(0, 999999); - // } catch (err) { - // inputEl.select(); // IE11 etc. - // } - // inputEl.contentEditable = oldContentEditable; - // inputEl.readOnly = oldReadOnly; - // document.execCommand('copy'); - // inputEl.blur(); + el.style.left = '-9999px'; // Move outside the screen to make it invisible + document.body.appendChild(el); // Append the <textarea> element to the HTML document + + el.select(); // Select the <textarea> content + document.execCommand('copy'); // Copy + document.body.removeChild(el); // Remove the <textarea> element + + if (selected) { // If a selection existed before copying + document.getSelection().removeAllRanges(); // Unselect everything on the HTML document + document.getSelection().addRange(selected); // Restore the original selection + } + } module.exports = { From 18e27398aa68584f96a21278fda31e0f1ed4df03 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 31 Jul 2018 15:59:32 +0100 Subject: [PATCH 101/760] reflect reviews --- components/x-gift-article/src/Buttons.jsx | 4 +-- components/x-gift-article/src/Form.jsx | 4 +-- components/x-gift-article/src/GiftArticle.jsx | 10 +++--- components/x-gift-article/src/Message.jsx | 6 ++-- components/x-gift-article/src/UrlSection.jsx | 10 +++--- components/x-gift-article/src/lib/api.js | 2 +- .../x-gift-article/src/lib/props-composer.js | 33 ++++++++++--------- .../src/lib/share-link-actions.js | 4 +-- 8 files changed, 38 insertions(+), 35 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index e35030d0a..332a9daf9 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -16,13 +16,13 @@ const ButtonWithGapClassNames = [ ].join(' '); -export default ({ isGift, isGiftUrlCreated, mailtoLink, createGiftUrl, copyGiftUrl, copyNonGiftUrl, showCopyButton }) => { +export default ({ isGift, isGiftUrlCreated, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, showCopyButton }) => { if (isGiftUrlCreated || !isGift) { return ( <div className={ ButtonsClassName }> { showCopyButton ? <button className={ ButtonWithGapClassNames } type="button" onClick={ isGift ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> : null } - <a className={ ButtonClassNames } href={ mailtoLink } target="_blank">Email link</a> + <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank">Email link</a> </div> ); } diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 3798987ea..3c927bb4d 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -30,10 +30,10 @@ export default (props) => ( isFreeArticle={ props.isFreeArticle } url={ props.url } urlType={ props.urlType } - credit={ props.credit } + giftCredits={ props.giftCredits } monthlyAllowance={ props.monthlyAllowance } dateText={ props.dateText } - mailtoLink={ props.mailtoLink } + mailtoUrl={ props.mailtoUrl } createGiftUrl={ props.actions.createGiftUrl } copyGiftUrl={ props.actions.copyGiftUrl } copyNonGiftUrl={ props.actions.copyNonGiftUrl } diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index db4492b88..d5fc9eb5a 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -6,7 +6,7 @@ import Form from './Form'; import api from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; -let hasAttempetedToGetAllowance = false; +let hasAttemptedToGetAllowance = false; let propsComposer; const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) => ({ @@ -40,8 +40,8 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = getAllowance() { return api.getGiftArticleAllowance() - .then(({ credit, monthlyAllowance }) => { - return propsComposer.setAllowance(credit, monthlyAllowance); + .then(({ giftCredits, monthlyAllowance }) => { + return propsComposer.setAllowance(giftCredits, monthlyAllowance); }) .catch(() => { // do something @@ -69,8 +69,8 @@ const BaseTemplate = (props) => { propsComposer = props.composer; } - if (!hasAttempetedToGetAllowance && !props.isFreeArticle) { - hasAttempetedToGetAllowance = true; + if (!hasAttemptedToGetAllowance && !props.isFreeArticle) { + hasAttemptedToGetAllowance = true; props.actions.getAllowance(); } diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index cea500028..3c470a4e6 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -4,7 +4,7 @@ import styles from './GiftArticle.css'; const messageClassName = styles.message; const boldTextClassName = styles.bold; -export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowance, dateText, redemptionLimit }) => { +export default ({ isGift, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, dateText, redemptionLimit }) => { if (isFreeArticle) { return ( @@ -15,7 +15,7 @@ export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowa } if (isGift) { - if (credit === 0) { + if (giftCredits === 0) { return ( <div className={ messageClassName }> You’ve used all your <span className={ boldTextClassName }>gift articles</span><br /> @@ -34,7 +34,7 @@ export default ({ isGift, isGiftUrlCreated, isFreeArticle, credit, monthlyAllowa return ( <div className={ messageClassName }> - You have <span className={ boldTextClassName }>{ credit } gift { credit === 1 ? 'article' : 'articles' }</span> left this month + You have <span className={ boldTextClassName }>{ giftCredits } gift { giftCredits === 1 ? 'article' : 'articles' }</span> left this month </div>); } diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index e3b80739c..6b829888f 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -4,10 +4,10 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, credit, monthlyAllowance, dateText, mailtoLink, createGiftUrl, copyGiftUrl, copyNonGiftUrl, redemptionLimit, showCopyButton }) => ( +export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, giftCredits, monthlyAllowance, dateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, redemptionLimit, showCopyButton }) => ( <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> - { credit === 0 && isGift ? null : <Url + { giftCredits === 0 && isGift ? null : <Url isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } url={ url } @@ -18,16 +18,16 @@ export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, c isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } isFreeArticle={ isFreeArticle } - credit={ credit } + giftCredits={ giftCredits } monthlyAllowance={ monthlyAllowance } dateText={ dateText } redemptionLimit={ redemptionLimit } /> - { credit === 0 && isGift ? null : <Buttons + { giftCredits === 0 && isGift ? null : <Buttons isGift={ isGift } isGiftUrlCreated={ isGiftUrlCreated } - mailtoLink={ mailtoLink } + mailtoUrl={ mailtoUrl } createGiftUrl={ createGiftUrl } copyGiftUrl={ copyGiftUrl } copyNonGiftUrl={ copyNonGiftUrl } diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index ffbccaa1a..9bdec6cec 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -5,7 +5,7 @@ module.exports = { return fetch('/article-email/credits', { credentials: 'same-origin' }) .then(response => response.json()) .then(json => { - return { monthlyAllowance: json.credits.allowance, credit: json.credits.remainingCredits }; + return { monthlyAllowance: json.credits.allowance, giftCredits: json.credits.remainingCredits }; }); }, getGiftUrl: (articleId, sessionId) => { diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index ac77fef1a..a6b23bfa1 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -1,19 +1,22 @@ -import { createMailtoLink } from './share-link-actions'; +import { createMailtoUrl } from './share-link-actions'; import getNextAllowanceDate from './get-next-allowance-date'; export class GiftArticlePropsComposer { constructor(props, isCopySupported) { - this.title = props.title || 'Share this article'; this.isFreeArticle = props.isFreeArticle; this.articleTitle = props.articleTitle; this.articleUrl = props.articleUrl; this.articleId = props.articleId; + + this.title = props.title || 'Share this article'; this.sessionId = props.sessionId; - this.isGiftUrlCreated = false; - this.credit = undefined; + this.giftCredits = undefined; this.monthlyAllowance = undefined; + this.showCopyButton = isCopySupported; this.showMobileShareLinks = props.showMobileShareLinks; + + this.isGiftUrlCreated = false; this.isGiftUrlShortened = false; this.isNonGiftUrlShortened = false; @@ -29,9 +32,9 @@ export class GiftArticlePropsComposer { nonGift: 'non-gift-link' }; - this.mailtoLinks = { + this.mailtoUrls = { gift: undefined, - nonGift: createMailtoLink(this.articleTitle, this.articleUrl) + nonGift: createMailtoUrl(this.articleTitle, this.articleUrl) }; this.types = { @@ -54,7 +57,7 @@ export class GiftArticlePropsComposer { isGift: this.isFreeArticle ? false : true, url: this.isFreeArticle ? this.urls.nonGift : this.urls.dummy, urlType: this.isFreeArticle ? this.urlTypes.nonGift : this.urlTypes.dummy, - mailtoLink: this.isFreeArticle ? this.mailtoLinks.nonGift : undefined, + mailtoUrl: this.isFreeArticle ? this.mailtoUrls.nonGift : undefined, isGiftUrlCreated: this.isGiftUrlCreated, type: this.isFreeArticle ? this.types.nonGift : this.types.gift, articleId: this.articleId, @@ -72,7 +75,7 @@ export class GiftArticlePropsComposer { isGift: true, url: this.urls.gift || this.urls.dummy, urlType: this.urls.gift ? this.urlTypes.gift : this.urlTypes.dummy, - mailtoLink: this.mailtoLinks.gift, + mailtoUrl: this.mailtoUrls.gift, type: this.types.gift, showCopyConfirmation: false }; @@ -83,7 +86,7 @@ export class GiftArticlePropsComposer { isGift: false, url: this.urls.nonGift, urlType: this.urlTypes.nonGift, - mailtoLink: this.mailtoLinks.nonGift, + mailtoUrl: this.mailtoUrls.nonGift, type: this.types.nonGift, showCopyConfirmation: false }; @@ -93,28 +96,28 @@ export class GiftArticlePropsComposer { this.urls.gift = url; this.isGiftUrlCreated = true; this.isGiftUrlShortened = isShortened; - this.mailtoLinks.gift = createMailtoLink(this.articleTitle, url); + this.mailtoUrls.gift = createMailtoUrl(this.articleTitle, url); return { isGiftUrlCreated: this.isGiftUrlCreated, url: this.urls.gift, urlType: this.urlTypes.gift, - mailtoLink: this.mailtoLinks.gift, + mailtoUrl: this.mailtoUrls.gift, redemptionLimit: limit }; } - setAllowance(credit, monthlyAllowance) { + setAllowance(giftCredits, monthlyAllowance) { let dateText = undefined; - this.credit = credit; + this.giftCredits = giftCredits; this.monthlyAllowance = monthlyAllowance; - if (credit === 0) { + if (giftCredits === 0) { const nextAllowanceDate = getNextAllowanceDate(); dateText = `${nextAllowanceDate.monthName} ${nextAllowanceDate.day}`; } return { - credit: this.credit, + giftCredits: this.giftCredits, monthlyAllowance: this.monthlyAllowance, dateText }; diff --git a/components/x-gift-article/src/lib/share-link-actions.js b/components/x-gift-article/src/lib/share-link-actions.js index ee6d87e52..43ecd26e8 100644 --- a/components/x-gift-article/src/lib/share-link-actions.js +++ b/components/x-gift-article/src/lib/share-link-actions.js @@ -1,4 +1,4 @@ -function createMailtoLink (articleTitle, shareUrl) { +function createMailtoUrl (articleTitle, shareUrl) { const subject = encodeURIComponent(articleTitle); const body = encodeURIComponent(shareUrl); @@ -32,6 +32,6 @@ function copyToClipboard (copyText) { } module.exports = { - createMailtoLink, + createMailtoUrl, copyToClipboard } From d8633c0f410420c685cf6a565f500868db3d10e8 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 31 Jul 2018 16:04:16 +0100 Subject: [PATCH 102/760] display~ => show~ --- components/x-gift-article/src/Form.jsx | 4 ++-- components/x-gift-article/src/GiftArticle.jsx | 10 +++++----- components/x-gift-article/src/RadioButtonsSection.jsx | 6 +++--- components/x-gift-article/src/lib/props-composer.js | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 3c927bb4d..43038365f 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -19,8 +19,8 @@ export default (props) => ( { props.isFreeArticle ? null : <RadioButtonsSection isGift={ props.isGift } - displayGiftUrlSection={ props.actions.displayGiftUrlSection } - displayNonGiftUrlSection={ props.actions.displayNonGiftUrlSection }/> + showGiftUrlSection={ props.actions.showGiftUrlSection } + showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> } <UrlSection diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index d5fc9eb5a..9c4c2a409 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -10,20 +10,20 @@ let hasAttemptedToGetAllowance = false; let propsComposer; const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) => ({ - displayGiftUrlSection() { - return propsComposer.displayGiftUrlSection(); + showGiftUrlSection() { + return propsComposer.showGiftUrlSection(); }, - displayNonGiftUrlSection() { + showNonGiftUrlSection() { if (propsComposer.isNonGiftUrlShortened) { - return propsComposer.displayNonGiftUrlSection(); + return propsComposer.showNonGiftUrlSection(); } else { return api.getShorterUrl(articleUrl) .then(({ url, isShortened }) => { if (isShortened) { propsComposer.setShortenedNonGiftUrl(url); } - return propsComposer.displayNonGiftUrlSection(); + return propsComposer.showNonGiftUrlSection(); }) } }, diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 855f32521..c07ce65f8 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -8,7 +8,7 @@ const radioSectionClassNames = [ styles['radio-button-section'] ].join(' '); -export default ({ isGift, displayGiftUrlSection, displayNonGiftUrlSection }) => ( +export default ({ isGift, showGiftUrlSection, showNonGiftUrlSection }) => ( <div className={ radioSectionClassNames }> <input @@ -18,7 +18,7 @@ export default ({ isGift, displayGiftUrlSection, displayNonGiftUrlSection }) => className="o-forms__radio" id="giftLink" checked={ isGift } - onChange={ displayGiftUrlSection }/> + onChange={ showGiftUrlSection }/> <label htmlFor="giftLink" className="o-forms__label"> with <span className={ boldTextClassName }>anyone</span> @@ -31,7 +31,7 @@ export default ({ isGift, displayGiftUrlSection, displayNonGiftUrlSection }) => className="o-forms__radio" id="nonGiftLink" checked={ !isGift } - onChange={ displayNonGiftUrlSection }/> + onChange={ showNonGiftUrlSection }/> <label htmlFor="nonGiftLink" className="o-forms__label"> with <span className={ boldTextClassName }>other subscribers</span> diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index a6b23bfa1..e50032d98 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -70,7 +70,7 @@ export class GiftArticlePropsComposer { }; } - displayGiftUrlSection() { + showGiftUrlSection() { return { isGift: true, url: this.urls.gift || this.urls.dummy, @@ -81,7 +81,7 @@ export class GiftArticlePropsComposer { }; } - displayNonGiftUrlSection() { + showNonGiftUrlSection() { return { isGift: false, url: this.urls.nonGift, From ab7f92660edecd434751d2709aec70548cb9bff8 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 31 Jul 2018 16:47:47 +0100 Subject: [PATCH 103/760] tidy up stories mock --- .../x-gift-article/stories/free-article.js | 23 ++++++++++++++++++- .../stories/with-gift-credits.js | 21 +++++++++-------- .../stories/without-gift-credits.js | 10 ++++++-- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index a603e7d10..e20d59a35 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -1,9 +1,11 @@ +const articleUrl = 'https://www.ft.com/content/blahblahblah'; + exports.title = 'Free article'; exports.data = { title: 'Share this article (free)', isFreeArticle: true, - articleUrl: 'https://www.ft.com/content/blahblah', + articleUrl, articleTitle: 'Title Title Title Title', }; @@ -17,3 +19,22 @@ exports.knobs = [ // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; + +exports.fetchMock = fetchMock => { + fetchMock + .get( + '/article-email/credits', + { 'credits': + { + 'allowance': 20, + 'consumedCredits': 5, + 'remainingCredits': 15, + 'renewalDate': '2018-08-01T00:00:00Z' + } + } + ) + .get( + `/article/shorten-url/${ encodeURIComponent(articleUrl) }`, + { shortenedUrl: 'https://shortened-non-gift-url' } + ); +}; diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 732eb6069..89cea7a2d 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -1,9 +1,12 @@ +const articleUrl = 'https://www.ft.com/content/blahblahblah'; +const articleUrlRedeemed = 'https://gift-url-redeemed'; + exports.title = 'With gift credits'; exports.data = { title: 'Share this article (with credit)', isFreeArticle: false, - articleUrl: 'article-url', + articleUrl, articleTitle: 'Title Title Title Title', articleId: 'article id', sessionId: 'session id', @@ -31,27 +34,25 @@ exports.fetchMock = fetchMock => { { 'credits': { 'allowance': 20, - 'consumedCredits': 19, - 'remainingCredits': 1, + 'consumedCredits': 5, + 'remainingCredits': 15, 'renewalDate': '2018-08-01T00:00:00Z' } } ) .get( - 'begin:/article/shorten-url/gift', + `/article/shorten-url/${ encodeURIComponent(articleUrlRedeemed) }`, { shortenedUrl: 'https://shortened-gift-url' } ) .get( - 'begin:/article/shorten-url/article', + `/article/shorten-url/${ encodeURIComponent(articleUrl) }`, { shortenedUrl: 'https://shortened-non-gift-url' } - ); - - fetchMock + ) .post( '/article-email/gift-link', { - 'redemptionUrl': 'gift-url-redeemed', - 'remainingAllowance': 2 + 'redemptionUrl': articleUrlRedeemed, + 'remainingAllowance': 1 } ); }; diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index 4c62e92d7..85bd0c818 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -1,9 +1,11 @@ +const articleUrl = 'https://www.ft.com/content/blahblahblah'; + exports.title = 'Without gift credits'; exports.data = { title: 'Share this article (without credit)', isFreeArticle: false, - articleUrl: 'https://www.ft.com/content/blahblah', + articleUrl, articleTitle: 'Title Title Title Title', }; @@ -29,6 +31,10 @@ exports.fetchMock = fetchMock => { 'remainingCredits': 0, 'renewalDate': '2018-08-01T00:00:00Z' } - }, + } + ) + .get( + `/article/shorten-url/${ encodeURIComponent(articleUrl) }`, + { shortenedUrl: 'https://shortened-non-gift-url' } ); }; From e4fa1113687cffde89c890acd9556cb514fe6ff3 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 2 Aug 2018 11:33:01 +0100 Subject: [PATCH 104/760] show shortened nonGift url when it's a free article --- components/x-gift-article/src/GiftArticle.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 9c4c2a409..244063bfd 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -6,6 +6,7 @@ import Form from './Form'; import api from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; +let hasAttemptedToShortenedNonGiftUrl = false; let hasAttemptedToGetAllowance = false; let propsComposer; @@ -69,7 +70,12 @@ const BaseTemplate = (props) => { propsComposer = props.composer; } - if (!hasAttemptedToGetAllowance && !props.isFreeArticle) { + if (props.isFreeArticle && !hasAttemptedToShortenedNonGiftUrl) { + hasAttemptedToShortenedNonGiftUrl = true; + props.actions.showNonGiftUrlSection(); + } + + if (!props.isFreeArticle && !hasAttemptedToGetAllowance) { hasAttemptedToGetAllowance = true; props.actions.getAllowance(); } From 3f94647e19ca8e4be2df372366e4ee3a67c13b41 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 2 Aug 2018 12:06:05 +0100 Subject: [PATCH 105/760] reflect review: using constants to apply share types --- components/x-gift-article/src/Buttons.jsx | 7 ++++--- components/x-gift-article/src/Form.jsx | 5 ++--- components/x-gift-article/src/Message.jsx | 5 +++-- .../x-gift-article/src/RadioButtonsSection.jsx | 7 ++++--- components/x-gift-article/src/Url.jsx | 5 +++-- components/x-gift-article/src/UrlSection.jsx | 15 ++++++++------- components/x-gift-article/src/lib/constants.js | 2 ++ .../x-gift-article/src/lib/props-composer.js | 15 ++++----------- 8 files changed, 30 insertions(+), 31 deletions(-) create mode 100644 components/x-gift-article/src/lib/constants.js diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 332a9daf9..3fdb5771b 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -1,4 +1,5 @@ import { h } from '@financial-times/x-engine'; +import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './lib/constants'; import styles from './GiftArticle.css'; const ButtonsClassName = styles.buttons; @@ -16,12 +17,12 @@ const ButtonWithGapClassNames = [ ].join(' '); -export default ({ isGift, isGiftUrlCreated, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, showCopyButton }) => { +export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, showCopyButton }) => { - if (isGiftUrlCreated || !isGift) { + if (isGiftUrlCreated || shareType === SHARE_TYPE_NON_GIFT) { return ( <div className={ ButtonsClassName }> - { showCopyButton ? <button className={ ButtonWithGapClassNames } type="button" onClick={ isGift ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> : null } + { showCopyButton ? <button className={ ButtonWithGapClassNames } type="button" onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> : null } <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank">Email link</a> </div> ); diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 43038365f..caa1ed7d5 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -18,14 +18,13 @@ export default (props) => ( <Title title={ props.title }/> { props.isFreeArticle ? null : <RadioButtonsSection - isGift={ props.isGift } + shareType={ props.shareType } showGiftUrlSection={ props.actions.showGiftUrlSection } showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> } <UrlSection - type={ props.type } - isGift={ props.isGift } + shareType={ props.shareType } isGiftUrlCreated={ props.isGiftUrlCreated } isFreeArticle={ props.isFreeArticle } url={ props.url } diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 3c470a4e6..3c8bad076 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -1,10 +1,11 @@ import { h } from '@financial-times/x-engine'; +import { SHARE_TYPE_GIFT } from './lib/constants'; import styles from './GiftArticle.css'; const messageClassName = styles.message; const boldTextClassName = styles.bold; -export default ({ isGift, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, dateText, redemptionLimit }) => { +export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, dateText, redemptionLimit }) => { if (isFreeArticle) { return ( @@ -14,7 +15,7 @@ export default ({ isGift, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyA ); } - if (isGift) { + if (shareType === SHARE_TYPE_GIFT) { if (giftCredits === 0) { return ( <div className={ messageClassName }> diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index c07ce65f8..76fd28894 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -1,4 +1,5 @@ import { h } from '@financial-times/x-engine'; +import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './lib/constants'; import styles from './GiftArticle.css'; const boldTextClassName = styles.bold; @@ -8,7 +9,7 @@ const radioSectionClassNames = [ styles['radio-button-section'] ].join(' '); -export default ({ isGift, showGiftUrlSection, showNonGiftUrlSection }) => ( +export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( <div className={ radioSectionClassNames }> <input @@ -17,7 +18,7 @@ export default ({ isGift, showGiftUrlSection, showNonGiftUrlSection }) => ( value="giftLink" className="o-forms__radio" id="giftLink" - checked={ isGift } + checked={ shareType === SHARE_TYPE_GIFT } onChange={ showGiftUrlSection }/> <label htmlFor="giftLink" className="o-forms__label"> @@ -30,7 +31,7 @@ export default ({ isGift, showGiftUrlSection, showNonGiftUrlSection }) => ( value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" - checked={ !isGift } + checked={ shareType === SHARE_TYPE_NON_GIFT } onChange={ showNonGiftUrlSection }/> <label htmlFor="nonGiftLink" className="o-forms__label"> diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 58ca81cc8..83fc0162b 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -1,4 +1,5 @@ import { h } from '@financial-times/x-engine'; +import { SHARE_TYPE_GIFT } from './lib/constants'; import styles from './GiftArticle.css'; const urlClassNames = [ @@ -6,14 +7,14 @@ const urlClassNames = [ styles.url ].join(' '); -export default ({ isGift, isGiftUrlCreated, url, urlType }) => { +export default ({ shareType, isGiftUrlCreated, url, urlType }) => { return ( <input type="text" name={ urlType } value={ url } className={ urlClassNames } - disabled={ isGift && !isGiftUrlCreated } + disabled={ shareType === SHARE_TYPE_GIFT && !isGiftUrlCreated } readOnly/> ); }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 6b829888f..c3cd6d912 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -1,21 +1,22 @@ import { h } from '@financial-times/x-engine'; +import { SHARE_TYPE_GIFT } from './lib/constants'; import Url from './Url'; import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, giftCredits, monthlyAllowance, dateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, redemptionLimit, showCopyButton }) => ( - <div className={ styles['url-section'] } data-section-id={ type } data-trackable={ type }> +export default ({ shareType, isGiftUrlCreated, isFreeArticle, url, urlType, giftCredits, monthlyAllowance, dateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, redemptionLimit, showCopyButton }) => ( + <div className={ styles['url-section'] } data-section-id={ shareType + 'Link' } data-trackable={ shareType + 'Link' }> - { giftCredits === 0 && isGift ? null : <Url - isGift={ isGift } + { giftCredits === 0 && shareType === SHARE_TYPE_GIFT ? null : <Url + shareType={ shareType } isGiftUrlCreated={ isGiftUrlCreated } url={ url } urlType={ urlType }/> } <Message - isGift={ isGift } + shareType={ shareType } isGiftUrlCreated={ isGiftUrlCreated } isFreeArticle={ isFreeArticle } giftCredits={ giftCredits } @@ -24,8 +25,8 @@ export default ({ type, isGift, isGiftUrlCreated, isFreeArticle, url, urlType, g redemptionLimit={ redemptionLimit } /> - { giftCredits === 0 && isGift ? null : <Buttons - isGift={ isGift } + { giftCredits === 0 && shareType === SHARE_TYPE_GIFT ? null : <Buttons + shareType={ shareType } isGiftUrlCreated={ isGiftUrlCreated } mailtoUrl={ mailtoUrl } createGiftUrl={ createGiftUrl } diff --git a/components/x-gift-article/src/lib/constants.js b/components/x-gift-article/src/lib/constants.js new file mode 100644 index 000000000..920b72481 --- /dev/null +++ b/components/x-gift-article/src/lib/constants.js @@ -0,0 +1,2 @@ +export const SHARE_TYPE_GIFT = 'gift'; +export const SHARE_TYPE_NON_GIFT = 'nonGift'; diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index e50032d98..eba12f31f 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -1,5 +1,6 @@ import { createMailtoUrl } from './share-link-actions'; import getNextAllowanceDate from './get-next-allowance-date'; +import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './constants'; export class GiftArticlePropsComposer { constructor(props, isCopySupported) { @@ -37,11 +38,6 @@ export class GiftArticlePropsComposer { nonGift: createMailtoUrl(this.articleTitle, this.articleUrl) }; - this.types = { - gift: 'giftLink', - nonGift: 'nonGiftLink' - }; - this.mobileShareLinks = this.showMobileShareLinks ? { facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(this.articleUrl)}&t=${encodeURIComponent(this.articleTitle)}`, twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(this.articleUrl)}&text=${encodeURIComponent(this.articleTitle)}&via=financialtimes`, @@ -54,12 +50,11 @@ export class GiftArticlePropsComposer { return { title: this.title, isFreeArticle: this.isFreeArticle, - isGift: this.isFreeArticle ? false : true, + shareType: this.isFreeArticle ? SHARE_TYPE_NON_GIFT : SHARE_TYPE_GIFT, url: this.isFreeArticle ? this.urls.nonGift : this.urls.dummy, urlType: this.isFreeArticle ? this.urlTypes.nonGift : this.urlTypes.dummy, mailtoUrl: this.isFreeArticle ? this.mailtoUrls.nonGift : undefined, isGiftUrlCreated: this.isGiftUrlCreated, - type: this.isFreeArticle ? this.types.nonGift : this.types.gift, articleId: this.articleId, articleUrl: this.articleUrl, sessionId: this.sessionId, @@ -72,22 +67,20 @@ export class GiftArticlePropsComposer { showGiftUrlSection() { return { - isGift: true, + shareType: SHARE_TYPE_GIFT, url: this.urls.gift || this.urls.dummy, urlType: this.urls.gift ? this.urlTypes.gift : this.urlTypes.dummy, mailtoUrl: this.mailtoUrls.gift, - type: this.types.gift, showCopyConfirmation: false }; } showNonGiftUrlSection() { return { - isGift: false, + shareType: SHARE_TYPE_NON_GIFT, url: this.urls.nonGift, urlType: this.urlTypes.nonGift, mailtoUrl: this.mailtoUrls.nonGift, - type: this.types.nonGift, showCopyConfirmation: false }; } From 9f657a9a792c4e2e6c61ff43cbe072edf7ca7342 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 2 Aug 2018 12:15:08 +0100 Subject: [PATCH 106/760] set shortend nonGift url in nonGift mailto url --- components/x-gift-article/src/lib/props-composer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index eba12f31f..6149ecdd1 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -118,6 +118,7 @@ export class GiftArticlePropsComposer { setShortenedNonGiftUrl(shortenedUrl) { this.isNonGiftUrlShortened = true; + this.mailtoUrls.nonGift = createMailtoUrl(this.articleTitle, shortenedUrl); this.urls.nonGift = shortenedUrl; } From 2d72c9a96d07d3cce0349ee0d6eebe0b49a8d0f4 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 2 Aug 2018 14:25:21 +0100 Subject: [PATCH 107/760] add tracking --- components/x-gift-article/src/Buttons.jsx | 4 +-- components/x-gift-article/src/Form.jsx | 2 ++ components/x-gift-article/src/GiftArticle.jsx | 19 +++++++++-- components/x-gift-article/src/UrlSection.jsx | 4 ++- components/x-gift-article/src/lib/tracking.js | 34 +++++++++++++++++++ 5 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 components/x-gift-article/src/lib/tracking.js diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 3fdb5771b..6889249da 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -17,13 +17,13 @@ const ButtonWithGapClassNames = [ ].join(' '); -export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, showCopyButton }) => { +export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, emailGiftUrl, emailNonGiftUrl, showCopyButton }) => { if (isGiftUrlCreated || shareType === SHARE_TYPE_NON_GIFT) { return ( <div className={ ButtonsClassName }> { showCopyButton ? <button className={ ButtonWithGapClassNames } type="button" onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> : null } - <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank">Email link</a> + <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" onClick={ shareType === SHARE_TYPE_GIFT ? emailGiftUrl : emailNonGiftUrl }>Email link</a> </div> ); } diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index caa1ed7d5..c7ff4efe9 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -36,6 +36,8 @@ export default (props) => ( createGiftUrl={ props.actions.createGiftUrl } copyGiftUrl={ props.actions.copyGiftUrl } copyNonGiftUrl={ props.actions.copyNonGiftUrl } + emailGiftUrl={ props.actions.emailGiftUrl } + emailNonGiftUrl={ props.actions.emailNonGiftUrl } redemptionLimit={ props.redemptionLimit } showCopyButton={ props.showCopyButton }/> diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 244063bfd..de265077c 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -1,10 +1,12 @@ import { h } from '@financial-times/x-engine'; import { withActions } from '@financial-times/x-interaction'; + import Loading from './Loading'; import Form from './Form'; import api from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; +import tracking from './lib/tracking'; let hasAttemptedToShortenedNonGiftUrl = false; let hasAttemptedToGetAllowance = false; @@ -34,6 +36,7 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = .then(({ redemptionUrl, redemptionLimit }) => { return api.getShorterUrl(redemptionUrl) .then(({ url, isShortened }) => { + tracking.createGiftLink(url, redemptionUrl); return propsComposer.setGiftUrl(url, redemptionLimit, isShortened); }) }) @@ -50,15 +53,27 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = }, copyGiftUrl() { - copyToClipboard(propsComposer.urls.gift); + const giftUrl = propsComposer.urls.gift; + copyToClipboard(giftUrl); + tracking.copyLink('giftLink', giftUrl); return propsComposer.showCopyConfirmation(); }, copyNonGiftUrl() { - copyToClipboard(propsComposer.urls.nonGift); + const nonGiftUrl = propsComposer.urls.nonGift; + copyToClipboard(nonGiftUrl); + tracking.copyLink('nonGiftLink', nonGiftUrl); return propsComposer.showCopyConfirmation(); }, + emailGiftUrl() { + tracking.emailLink('giftLink', propsComposer.urls.gift); + }, + + emailNonGiftUrl() { + tracking.emailLink('nonGiftLink', propsComposer.urls.nonGift); + }, + hideCopyConfirmation() { return propsComposer.hideCopyConfirmation(); } diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index c3cd6d912..92d041266 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -5,7 +5,7 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ shareType, isGiftUrlCreated, isFreeArticle, url, urlType, giftCredits, monthlyAllowance, dateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, redemptionLimit, showCopyButton }) => ( +export default ({ shareType, isGiftUrlCreated, isFreeArticle, url, urlType, giftCredits, monthlyAllowance, dateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, emailGiftUrl, emailNonGiftUrl, redemptionLimit, showCopyButton }) => ( <div className={ styles['url-section'] } data-section-id={ shareType + 'Link' } data-trackable={ shareType + 'Link' }> { giftCredits === 0 && shareType === SHARE_TYPE_GIFT ? null : <Url @@ -32,6 +32,8 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, url, urlType, gift createGiftUrl={ createGiftUrl } copyGiftUrl={ copyGiftUrl } copyNonGiftUrl={ copyNonGiftUrl } + emailGiftUrl={ emailGiftUrl } + emailNonGiftUrl={ emailNonGiftUrl } showCopyButton={ showCopyButton }/> } diff --git a/components/x-gift-article/src/lib/tracking.js b/components/x-gift-article/src/lib/tracking.js new file mode 100644 index 000000000..4575d50a4 --- /dev/null +++ b/components/x-gift-article/src/lib/tracking.js @@ -0,0 +1,34 @@ +function dispatchEvent (detail) { + const event = new CustomEvent('oTracking.event', { + detail, + bubbles: true + }); + + document.body.dispatchEvent(event); +} + +module.exports = { + + createGiftLink: (link, longUrl) => dispatchEvent({ + category: 'gift-link', + action: 'create', + linkType: 'giftLink', + link, + longUrl + }), + + copyLink: (linkType, link) => dispatchEvent({ + category: 'gift-link', + action: 'copy', + linkType, + link + }), + + emailLink: (linkType, link) => dispatchEvent({ + category: 'gift-link', + action: 'mailto', + linkType, + link + }) + +}; From 0b5986df07cfb73590e18ae63d972f48c7551e6d Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 2 Aug 2018 15:20:28 +0100 Subject: [PATCH 108/760] refactor --- components/x-gift-article/src/Buttons.jsx | 2 +- components/x-gift-article/src/Form.jsx | 10 +-- components/x-gift-article/src/GiftArticle.jsx | 11 ++- components/x-gift-article/src/Message.jsx | 12 ++-- components/x-gift-article/src/UrlSection.jsx | 68 +++++++++++-------- .../x-gift-article/src/lib/props-composer.js | 15 ++-- 6 files changed, 72 insertions(+), 46 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 6889249da..209b2e34c 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -22,7 +22,7 @@ export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, copyGif if (isGiftUrlCreated || shareType === SHARE_TYPE_NON_GIFT) { return ( <div className={ ButtonsClassName }> - { showCopyButton ? <button className={ ButtonWithGapClassNames } type="button" onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> : null } + { showCopyButton && <button className={ ButtonWithGapClassNames } type="button" onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> } <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" onClick={ shareType === SHARE_TYPE_GIFT ? emailGiftUrl : emailNonGiftUrl }>Email link</a> </div> ); diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index c7ff4efe9..868ac416c 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -17,7 +17,7 @@ export default (props) => ( <Title title={ props.title }/> - { props.isFreeArticle ? null : <RadioButtonsSection + { !props.isFreeArticle && <RadioButtonsSection shareType={ props.shareType } showGiftUrlSection={ props.actions.showGiftUrlSection } showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> @@ -43,11 +43,11 @@ export default (props) => ( </fieldset> - { props.showCopyConfirmation ? <CopyConfirmation hideCopyConfirmation={ props.actions.hideCopyConfirmation }/> : null } + { props.showCopyConfirmation && + <CopyConfirmation hideCopyConfirmation={ props.actions.hideCopyConfirmation }/> } - { props.showShareButtons ? - <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> : null - } + { props.showShareButtons && + <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> } </form> ); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index de265077c..0418aa5cc 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -52,6 +52,15 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = }) }, + getShorterNonGiftUrl() { + return api.getShorterUrl(articleUrl) + .then(({ url, isShortened }) => { + if (isShortened) { + return propsComposer.setShortenedNonGiftUrl(url); + } + }) + }, + copyGiftUrl() { const giftUrl = propsComposer.urls.gift; copyToClipboard(giftUrl); @@ -87,7 +96,7 @@ const BaseTemplate = (props) => { if (props.isFreeArticle && !hasAttemptedToShortenedNonGiftUrl) { hasAttemptedToShortenedNonGiftUrl = true; - props.actions.showNonGiftUrlSection(); + props.actions.getShorterNonGiftUrl(); } if (!props.isFreeArticle && !hasAttemptedToGetAllowance) { diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 3c8bad076..ce064136a 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { SHARE_TYPE_GIFT } from './lib/constants'; +import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './lib/constants'; import styles from './GiftArticle.css'; const messageClassName = styles.message; @@ -39,10 +39,12 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month </div>); } - return ( - <div className={ messageClassName }> + if (shareType === SHARE_TYPE_NON_GIFT) { + return ( + <div className={ messageClassName }> This link can only be read by existing subscribers - </div> - ); + </div> + ); + } }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 92d041266..77835e4c2 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -5,37 +5,49 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; -export default ({ shareType, isGiftUrlCreated, isFreeArticle, url, urlType, giftCredits, monthlyAllowance, dateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, emailGiftUrl, emailNonGiftUrl, redemptionLimit, showCopyButton }) => ( - <div className={ styles['url-section'] } data-section-id={ shareType + 'Link' } data-trackable={ shareType + 'Link' }> +export default ({ shareType, isGiftUrlCreated, isFreeArticle, + url, urlType, giftCredits, monthlyAllowance, dateText, + mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, + emailGiftUrl, emailNonGiftUrl, redemptionLimit, showCopyButton }) => { - { giftCredits === 0 && shareType === SHARE_TYPE_GIFT ? null : <Url - shareType={ shareType } - isGiftUrlCreated={ isGiftUrlCreated } - url={ url } - urlType={ urlType }/> - } + const hideUrlShareElements = ( giftCredits === 0 && shareType === SHARE_TYPE_GIFT ); + const showUrlShareElements = !hideUrlShareElements; - <Message - shareType={ shareType } - isGiftUrlCreated={ isGiftUrlCreated } - isFreeArticle={ isFreeArticle } - giftCredits={ giftCredits } - monthlyAllowance={ monthlyAllowance } - dateText={ dateText } - redemptionLimit={ redemptionLimit } - /> + return ( + <div + className={ styles['url-section'] } + data-section-id={ shareType + 'Link' } + data-trackable={ shareType + 'Link' }> - { giftCredits === 0 && shareType === SHARE_TYPE_GIFT ? null : <Buttons + { showUrlShareElements && <Url + shareType={ shareType } + isGiftUrlCreated={ isGiftUrlCreated } + url={ url } + urlType={ urlType }/> + } + + <Message shareType={ shareType } isGiftUrlCreated={ isGiftUrlCreated } - mailtoUrl={ mailtoUrl } - createGiftUrl={ createGiftUrl } - copyGiftUrl={ copyGiftUrl } - copyNonGiftUrl={ copyNonGiftUrl } - emailGiftUrl={ emailGiftUrl } - emailNonGiftUrl={ emailNonGiftUrl } - showCopyButton={ showCopyButton }/> - } + isFreeArticle={ isFreeArticle } + giftCredits={ giftCredits } + monthlyAllowance={ monthlyAllowance } + dateText={ dateText } + redemptionLimit={ redemptionLimit } + /> + + { showUrlShareElements && <Buttons + shareType={ shareType } + isGiftUrlCreated={ isGiftUrlCreated } + mailtoUrl={ mailtoUrl } + createGiftUrl={ createGiftUrl } + copyGiftUrl={ copyGiftUrl } + copyNonGiftUrl={ copyNonGiftUrl } + emailGiftUrl={ emailGiftUrl } + emailNonGiftUrl={ emailNonGiftUrl } + showCopyButton={ showCopyButton }/> + } - </div> -); + </div> + ); +}; diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 6149ecdd1..15db22a9a 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -47,22 +47,20 @@ export class GiftArticlePropsComposer { } getDefault() { - return { + const fundamentalProps = { title: this.title, isFreeArticle: this.isFreeArticle, - shareType: this.isFreeArticle ? SHARE_TYPE_NON_GIFT : SHARE_TYPE_GIFT, - url: this.isFreeArticle ? this.urls.nonGift : this.urls.dummy, - urlType: this.isFreeArticle ? this.urlTypes.nonGift : this.urlTypes.dummy, - mailtoUrl: this.isFreeArticle ? this.mailtoUrls.nonGift : undefined, isGiftUrlCreated: this.isGiftUrlCreated, articleId: this.articleId, articleUrl: this.articleUrl, sessionId: this.sessionId, showCopyButton: this.showCopyButton, - showCopyConfirmation: false, showShareButtons: this.showMobileShareLinks, mobileShareLinks: this.mobileShareLinks }; + const additionalProps = this.isFreeArticle ? this.showNonGiftUrlSection() : this.showGiftUrlSection(); + + return Object.assign({}, fundamentalProps, additionalProps); } showGiftUrlSection() { @@ -120,6 +118,11 @@ export class GiftArticlePropsComposer { this.isNonGiftUrlShortened = true; this.mailtoUrls.nonGift = createMailtoUrl(this.articleTitle, shortenedUrl); this.urls.nonGift = shortenedUrl; + + return { + url: this.urls.nonGift, + mailtoUrl: this.mailtoUrls.nonGift + }; } showCopyConfirmation() { From 8239b468a61a2cf1e310c040a1dfc13e8c1ea562 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 2 Aug 2018 18:15:02 +0100 Subject: [PATCH 109/760] use async await --- components/x-gift-article/src/GiftArticle.jsx | 65 +++++++------- components/x-gift-article/src/lib/api.js | 88 +++++++++++-------- 2 files changed, 83 insertions(+), 70 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 0418aa5cc..7fc989a97 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -17,54 +17,54 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = return propsComposer.showGiftUrlSection(); }, - showNonGiftUrlSection() { + async showNonGiftUrlSection() { if (propsComposer.isNonGiftUrlShortened) { return propsComposer.showNonGiftUrlSection(); } else { - return api.getShorterUrl(articleUrl) - .then(({ url, isShortened }) => { - if (isShortened) { - propsComposer.setShortenedNonGiftUrl(url); - } - return propsComposer.showNonGiftUrlSection(); - }) + const { url, isShortened } = await api.getShorterUrl(articleUrl); + if (isShortened) { + propsComposer.setShortenedNonGiftUrl(url); + } + return propsComposer.showNonGiftUrlSection(); } }, - createGiftUrl() { - return api.getGiftUrl(articleId, sessionId) - .then(({ redemptionUrl, redemptionLimit }) => { - return api.getShorterUrl(redemptionUrl) - .then(({ url, isShortened }) => { - tracking.createGiftLink(url, redemptionUrl); - return propsComposer.setGiftUrl(url, redemptionLimit, isShortened); - }) - }) + async createGiftUrl() { + const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(articleId, sessionId); + + if (redemptionUrl) { + const { url, isShortened } = await api.getShorterUrl(redemptionUrl); + tracking.createGiftLink(url, redemptionUrl); + return propsComposer.setGiftUrl(url, redemptionLimit, isShortened); + } else { + // TODO do something + } }, - getAllowance() { - return api.getGiftArticleAllowance() - .then(({ giftCredits, monthlyAllowance }) => { - return propsComposer.setAllowance(giftCredits, monthlyAllowance); - }) - .catch(() => { - // do something - }) + async getAllowance() { + const { giftCredits, monthlyAllowance } = await api.getGiftArticleAllowance(); + + // avoid to use giftCredits >= 0 because it returns true when null and "" + if (giftCredits > 0 || giftCredits === 0) { + return propsComposer.setAllowance(giftCredits, monthlyAllowance); + } else { + // TODO do something + } }, - getShorterNonGiftUrl() { - return api.getShorterUrl(articleUrl) - .then(({ url, isShortened }) => { - if (isShortened) { - return propsComposer.setShortenedNonGiftUrl(url); - } - }) + async getShorterNonGiftUrl() { + const { url, isShortened } = await api.getShorterUrl(articleUrl); + + if (isShortened) { + return propsComposer.setShortenedNonGiftUrl(url); + } }, copyGiftUrl() { const giftUrl = propsComposer.urls.gift; copyToClipboard(giftUrl); tracking.copyLink('giftLink', giftUrl); + return propsComposer.showCopyConfirmation(); }, @@ -72,6 +72,7 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = const nonGiftUrl = propsComposer.urls.nonGift; copyToClipboard(nonGiftUrl); tracking.copyLink('nonGiftLink', nonGiftUrl); + return propsComposer.showCopyConfirmation(); }, diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 9bdec6cec..fda4e223c 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -1,49 +1,61 @@ const REDEMPTION_LIMIT = 3; module.exports = { - getGiftArticleAllowance: () => { - return fetch('/article-email/credits', { credentials: 'same-origin' }) - .then(response => response.json()) - .then(json => { - return { monthlyAllowance: json.credits.allowance, giftCredits: json.credits.remainingCredits }; - }); + getGiftArticleAllowance: async () => { + try { + const response = await fetch('/article-email/credits', { credentials: 'same-origin' }); + const json = await response.json(); + + return { + monthlyAllowance: json.credits.allowance, + giftCredits: json.credits.remainingCredits + }; + + } catch (e) { + return { monthlyAllowance: undefined, giftCredits: undefined }; + } + }, - getGiftUrl: (articleId, sessionId) => { - return fetch('/article-email/gift-link', { - credentials: 'same-origin', - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - contentUUID: articleId, - ftSessionSecure: sessionId - }) - }) - .then(response => response.ok ? response.json() : {}) - .then(body => { - if (body.errors) { - throw new Error(`Failed to get gift article link: ${body.errors.join(', ')}`); - } - - return Object.assign({}, body, { redemptionLimit: REDEMPTION_LIMIT }); + + getGiftUrl: async (articleId, sessionId) => { + try { + const response = await fetch('/article-email/gift-link', { + credentials: 'same-origin', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + contentUUID: articleId, + ftSessionSecure: sessionId + }) }); + + const body = response.ok ? await response.json() : {}; + if (body.errors) { + throw new Error(`Failed to get gift article link: ${body.errors.join(', ')}`); + } + + return Object.assign({}, body, { redemptionLimit: REDEMPTION_LIMIT }); + + } catch (e) { + return { redemptionUrl: undefined, redemptionLimit: undefined }; + } }, - getShorterUrl: (originalUrl) => { + + getShorterUrl: async (originalUrl) => { let url = originalUrl; let isShortened = false; - return fetch('/article/shorten-url/' + encodeURIComponent(originalUrl), { credentials: 'same-origin' }) - .then(response => response.json()) - .then(json => { - if (json.shortenedUrl) { - isShortened = true; - url = json.shortenedUrl; - } - return { url, isShortened }; - }) - .catch(() => { - return { url, isShortened }; - }); + try { + const fetchUrl = '/article/shorten-url/' + encodeURIComponent(originalUrl); + const response = await fetch(fetchUrl, { credentials: 'same-origin' }); + const json = await response.json(); + + if (json.shortenedUrl) { + isShortened = true; + url = json.shortenedUrl; + } + } catch (e) {} + + return { url, isShortened }; }, } From e640507f4821063bff2a9e16be1a3c499eba00a4 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 13 Aug 2018 14:05:39 +0100 Subject: [PATCH 110/760] =?UTF-8?q?after=20rebase=20master=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/package.json | 10 +++++----- components/x-gift-article/rollup.config.js | 6 ------ components/x-gift-article/rollup.js | 4 ++++ 3 files changed, 9 insertions(+), 11 deletions(-) delete mode 100644 components/x-gift-article/rollup.config.js create mode 100644 components/x-gift-article/rollup.js diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 74045ebd3..8e8a7d802 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -1,6 +1,6 @@ { "name": "@financial-times/x-gift-article", - "version": "1.0.0", + "version": "0.0.0", "description": "This module provides gift article form", "main": "dist/GiftArticleWrapper.cjs.js", "browser": "dist/GiftArticleWrapper.es5.js", @@ -8,20 +8,20 @@ "style": "dist/GiftArticle.css", "scripts": { "prepare": "npm run build", - "build": "rollup -c rollup.config.js", - "start": "rollup --watch -c rollup.config.js" + "build": "node rollup.js", + "start": "node rollup.js --watch" }, - "keywords": [], + "keywords": ["x-dash"], "author": "", "license": "ISC", "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", "@financial-times/x-interaction": "file:../x-interaction", - "@financial-times/x-rollup": "file:../../packages/x-rollup", "classnames": "^2.2.6", "rollup": "^0.57.1" }, "devDependencies": { + "@financial-times/x-rollup": "file:../../packages/x-rollup", "node-sass": "^4.9.2" } } diff --git a/components/x-gift-article/rollup.config.js b/components/x-gift-article/rollup.config.js deleted file mode 100644 index 8f0f25c75..000000000 --- a/components/x-gift-article/rollup.config.js +++ /dev/null @@ -1,6 +0,0 @@ -import xRollup from '@financial-times/x-rollup'; -import pkg from './package.json'; - -const input = 'src/GiftArticleWrapper.jsx'; - -export default xRollup({input, pkg}); diff --git a/components/x-gift-article/rollup.js b/components/x-gift-article/rollup.js new file mode 100644 index 000000000..30a17f3cd --- /dev/null +++ b/components/x-gift-article/rollup.js @@ -0,0 +1,4 @@ +const xRollup = require('@financial-times/x-rollup'); +const pkg = require('./package.json'); + +xRollup({ input: './src/GiftArticleWrapper.jsx', pkg }); From bb5fafc88b52ae007b51688a4eeb56edb40d5ef8 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 13 Aug 2018 15:32:37 +0100 Subject: [PATCH 111/760] =?UTF-8?q?delete=20knobs=20=20=F0=9F=90=BF=20v2.1?= =?UTF-8?q?0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/stories/free-article.js | 7 ------- components/x-gift-article/stories/index.js | 4 +++- components/x-gift-article/stories/knobs.js | 9 --------- components/x-gift-article/stories/with-gift-credits.js | 10 ---------- .../x-gift-article/stories/without-gift-credits.js | 7 ------- 5 files changed, 3 insertions(+), 34 deletions(-) delete mode 100644 components/x-gift-article/stories/knobs.js diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index e20d59a35..401e6f9c1 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -9,13 +9,6 @@ exports.data = { articleTitle: 'Title Title Title Title', }; -exports.knobs = [ - 'title', - 'isFreeArticle', - 'articleUrl', - 'articleTitle' -]; - // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 4cf9782ba..79f014bbb 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -1,7 +1,9 @@ const { GiftArticleWrapper } = require('../'); exports.component = GiftArticleWrapper; + exports.package = require('../package.json'); + exports.dependencies = { 'o-fonts': '^3.0.0', 'o-buttons': '^5.13.1', @@ -10,9 +12,9 @@ exports.dependencies = { 'o-share': '^6.2.0', 'o-message': '^2.3.3' }; + exports.stories = [ require('./with-gift-credits'), require('./without-gift-credits'), require('./free-article') ]; -exports.knobs = require('./knobs'); diff --git a/components/x-gift-article/stories/knobs.js b/components/x-gift-article/stories/knobs.js deleted file mode 100644 index 6f9ef7dac..000000000 --- a/components/x-gift-article/stories/knobs.js +++ /dev/null @@ -1,9 +0,0 @@ -// To ensure that component stories do not need to depend on Storybook themselves we return a -// function that may be passed the required dependencies. -module.exports = (data, { object, text, number, boolean, date, selectV2 }) => { - return { - title() { - return text('Title', data.title); - } - } -}; diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 89cea7a2d..f26e47f1c 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -13,16 +13,6 @@ exports.data = { showMobileShareLinks: true }; -exports.knobs = [ - 'title', - 'isFreeArticle', - 'articleUrl', - 'articleTitle', - 'articleId', - 'sessionId', - 'showMobileShareLinks' -]; - // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index 85bd0c818..71b76a46f 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -9,13 +9,6 @@ exports.data = { articleTitle: 'Title Title Title Title', }; -exports.knobs = [ - 'title', - 'isFreeArticle', - 'articleUrl', - 'articleTitle' -]; - // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; From 473098c2c1b669901eb697465cea00919278cad8 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 13 Aug 2018 16:22:57 +0100 Subject: [PATCH 112/760] =?UTF-8?q?add=20nativeShare=20property=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Buttons.jsx | 13 ++++- components/x-gift-article/src/Form.jsx | 4 +- components/x-gift-article/src/GiftArticle.jsx | 4 ++ components/x-gift-article/src/UrlSection.jsx | 7 ++- .../x-gift-article/src/lib/props-composer.js | 6 ++- components/x-gift-article/stories/index.js | 3 +- .../x-gift-article/stories/native-share.js | 48 +++++++++++++++++++ 7 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 components/x-gift-article/stories/native-share.js diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 209b2e34c..eafa25a32 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -17,9 +17,20 @@ const ButtonWithGapClassNames = [ ].join(' '); -export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, emailGiftUrl, emailNonGiftUrl, showCopyButton }) => { +export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, + copyGiftUrl, copyNonGiftUrl, emailGiftUrl, emailNonGiftUrl, + showCopyButton, showNativeShareButton, shareByNativeShare }) => { if (isGiftUrlCreated || shareType === SHARE_TYPE_NON_GIFT) { + + if (showNativeShareButton) { + return ( + <div className={ ButtonsClassName }> + <button className={ ButtonWithGapClassNames } type="button" onClick={ shareByNativeShare }>Share link</button> + </div> + ); + } + return ( <div className={ ButtonsClassName }> { showCopyButton && <button className={ ButtonWithGapClassNames } type="button" onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> } diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 868ac416c..43608f4b2 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -39,7 +39,9 @@ export default (props) => ( emailGiftUrl={ props.actions.emailGiftUrl } emailNonGiftUrl={ props.actions.emailNonGiftUrl } redemptionLimit={ props.redemptionLimit } - showCopyButton={ props.showCopyButton }/> + showCopyButton={ props.showCopyButton } + showNativeShareButton={ props.showNativeShareButton } + shareByNativeShare={ props.actions.shareByNativeShare }/> </fieldset> diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 7fc989a97..4760aa363 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -86,6 +86,10 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = hideCopyConfirmation() { return propsComposer.hideCopyConfirmation(); + }, + + shareByNativeShare() { + // TODO display native share } })); diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 77835e4c2..c88f61aca 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -8,7 +8,8 @@ import styles from './GiftArticle.css'; export default ({ shareType, isGiftUrlCreated, isFreeArticle, url, urlType, giftCredits, monthlyAllowance, dateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, - emailGiftUrl, emailNonGiftUrl, redemptionLimit, showCopyButton }) => { + emailGiftUrl, emailNonGiftUrl, redemptionLimit, showCopyButton, + showNativeShareButton, shareByNativeShare }) => { const hideUrlShareElements = ( giftCredits === 0 && shareType === SHARE_TYPE_GIFT ); const showUrlShareElements = !hideUrlShareElements; @@ -45,7 +46,9 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, copyNonGiftUrl={ copyNonGiftUrl } emailGiftUrl={ emailGiftUrl } emailNonGiftUrl={ emailNonGiftUrl } - showCopyButton={ showCopyButton }/> + showCopyButton={ showCopyButton } + showNativeShareButton={ showNativeShareButton } + shareByNativeShare={ shareByNativeShare }/> } </div> diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 15db22a9a..8da82c935 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -14,8 +14,9 @@ export class GiftArticlePropsComposer { this.giftCredits = undefined; this.monthlyAllowance = undefined; - this.showCopyButton = isCopySupported; - this.showMobileShareLinks = props.showMobileShareLinks; + this.showCopyButton = props.nativeShare ? false : isCopySupported; + this.showMobileShareLinks = props.nativeShare ? false : props.showMobileShareLinks; + this.showNativeShareButton = props.nativeShare; this.isGiftUrlCreated = false; this.isGiftUrlShortened = false; @@ -56,6 +57,7 @@ export class GiftArticlePropsComposer { sessionId: this.sessionId, showCopyButton: this.showCopyButton, showShareButtons: this.showMobileShareLinks, + showNativeShareButton: this.showNativeShareButton, mobileShareLinks: this.mobileShareLinks }; const additionalProps = this.isFreeArticle ? this.showNonGiftUrlSection() : this.showGiftUrlSection(); diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 79f014bbb..873264c8e 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -16,5 +16,6 @@ exports.dependencies = { exports.stories = [ require('./with-gift-credits'), require('./without-gift-credits'), - require('./free-article') + require('./free-article'), + require('./native-share') ]; diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/stories/native-share.js new file mode 100644 index 000000000..30bf41bf4 --- /dev/null +++ b/components/x-gift-article/stories/native-share.js @@ -0,0 +1,48 @@ +const articleUrl = 'https://www.ft.com/content/blahblahblah'; +const articleUrlRedeemed = 'https://gift-url-redeemed'; + +exports.title = 'With native share on App'; + +exports.data = { + title: 'Share this article (on App)', + isFreeArticle: false, + articleUrl, + articleTitle: 'Title Title Title Title', + articleId: 'article id', + sessionId: 'session id', + nativeShare: true +}; + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module; + +exports.fetchMock = fetchMock => { + fetchMock + .get( + '/article-email/credits', + { 'credits': + { + 'allowance': 20, + 'consumedCredits': 2, + 'remainingCredits': 18, + 'renewalDate': '2018-08-01T00:00:00Z' + } + } + ) + .get( + `/article/shorten-url/${ encodeURIComponent(articleUrlRedeemed) }`, + { shortenedUrl: 'https://shortened-gift-url' } + ) + .get( + `/article/shorten-url/${ encodeURIComponent(articleUrl) }`, + { shortenedUrl: 'https://shortened-non-gift-url' } + ) + .post( + '/article-email/gift-link', + { + 'redemptionUrl': articleUrlRedeemed, + 'remainingAllowance': 1 + } + ); +}; From a8792f74154129b4dba8788e27b56d1cfad75344 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 13 Aug 2018 17:01:17 +0100 Subject: [PATCH 113/760] =?UTF-8?q?add=20basic=20README=20=20=F0=9F=90=BF?= =?UTF-8?q?=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/readme.md | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 components/x-gift-article/readme.md diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md new file mode 100644 index 000000000..31c1b74f1 --- /dev/null +++ b/components/x-gift-article/readme.md @@ -0,0 +1,43 @@ +# x-gift-article + +This module provides a gift article form. + +## Installation + +This module is compatible with Node 6+ and is distributed on npm. + +```bash +npm install --save @financial-times/x-gift-article +``` + +[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine + +## Usage + +Component provided by this module expects a map of [gift article properties](#properties). They can be used with vanilla JavaScript or JSX (if you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: + +```jsx +import React from 'react'; +import { GiftArticle } from '@financial-times/x-gift-article'; + +// A == B == C +const a = GiftArticle(props); +const b = <GiftArticle {...props} />; +const c = React.createElement(GiftArticle, props); +``` + +All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. + +[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ + +### Properties + +Property | Type | Required | Note +--------------------------|---------|----------|---- +`isFreeArticle` | Boolean | yes | Only non gift form is displayed when this value is `true` +`articleUrl` | String | yes | Canonical URL +`articleTitle` | String | yes | +`articleId` | String | yes | Content UUID +`sessionId` | String | yes | This is needed to get a gift url +`showMobileShareLinks` | Boolean | no | This value will be `false` when `nativeShare` is `true` +`nativeShare` | Boolean | no | This is a property for App to display Native Sharing From 0c59e37ca3d6559cc505600f9a6583e1a809ec46 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 14 Aug 2018 10:48:16 +0100 Subject: [PATCH 114/760] =?UTF-8?q?pass=20isCopySupported=20property=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/readme.md | 9 +++++---- components/x-gift-article/src/GiftArticleWrapper.jsx | 3 +-- components/x-gift-article/src/lib/props-composer.js | 4 ++-- components/x-gift-article/stories/free-article.js | 1 + components/x-gift-article/stories/with-gift-credits.js | 3 ++- .../x-gift-article/stories/without-gift-credits.js | 1 + 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 31c1b74f1..5da93be0b 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -34,10 +34,11 @@ All `x-` components are designed to be compatible with a variety of runtimes, no Property | Type | Required | Note --------------------------|---------|----------|---- -`isFreeArticle` | Boolean | yes | Only non gift form is displayed when this value is `true` +`isFreeArticle` | Boolean | yes | Only non gift form is displayed when this value is `true`. `articleUrl` | String | yes | Canonical URL `articleTitle` | String | yes | `articleId` | String | yes | Content UUID -`sessionId` | String | yes | This is needed to get a gift url -`showMobileShareLinks` | Boolean | no | This value will be `false` when `nativeShare` is `true` -`nativeShare` | Boolean | no | This is a property for App to display Native Sharing +`sessionId` | String | yes | This is needed to get a gift url. +`showMobileShareLinks` | Boolean | no | This value will be `false` when `nativeShare` is `true`. +`isCopySupported` | Boolean | no | This value will be `false` when `nativeShare` is `true`. Pass whether the browser is supported the copy command or not. e.g. => `document.queryCommandSupported && document.queryCommandSupported('copy')` +`nativeShare` | Boolean | no | This is a property for App to display Native Sharing. diff --git a/components/x-gift-article/src/GiftArticleWrapper.jsx b/components/x-gift-article/src/GiftArticleWrapper.jsx index 25a189394..6dcb051a3 100644 --- a/components/x-gift-article/src/GiftArticleWrapper.jsx +++ b/components/x-gift-article/src/GiftArticleWrapper.jsx @@ -3,8 +3,7 @@ import { GiftArticlePropsComposer } from './lib/props-composer'; import { GiftArticle } from './GiftArticle'; const GiftArticleWrapper = (props) => { - const isCopySupported = document.queryCommandSupported && document.queryCommandSupported('copy'); - const propsComposer = new GiftArticlePropsComposer(props, isCopySupported); + const propsComposer = new GiftArticlePropsComposer(props); const composedProps = propsComposer.getDefault(); composedProps.composer = propsComposer; diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 8da82c935..5378209e9 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -3,7 +3,7 @@ import getNextAllowanceDate from './get-next-allowance-date'; import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './constants'; export class GiftArticlePropsComposer { - constructor(props, isCopySupported) { + constructor(props) { this.isFreeArticle = props.isFreeArticle; this.articleTitle = props.articleTitle; this.articleUrl = props.articleUrl; @@ -14,7 +14,7 @@ export class GiftArticlePropsComposer { this.giftCredits = undefined; this.monthlyAllowance = undefined; - this.showCopyButton = props.nativeShare ? false : isCopySupported; + this.showCopyButton = props.nativeShare ? false : props.isCopySupported; this.showMobileShareLinks = props.nativeShare ? false : props.showMobileShareLinks; this.showNativeShareButton = props.nativeShare; diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index 401e6f9c1..4cca9e0a2 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -7,6 +7,7 @@ exports.data = { isFreeArticle: true, articleUrl, articleTitle: 'Title Title Title Title', + isCopySupported: true }; // This reference is only required for hot module loading in development diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index f26e47f1c..7f9e142db 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -10,7 +10,8 @@ exports.data = { articleTitle: 'Title Title Title Title', articleId: 'article id', sessionId: 'session id', - showMobileShareLinks: true + showMobileShareLinks: true, + isCopySupported: true }; // This reference is only required for hot module loading in development diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index 71b76a46f..89edb5be2 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -7,6 +7,7 @@ exports.data = { isFreeArticle: false, articleUrl, articleTitle: 'Title Title Title Title', + isCopySupported: true }; // This reference is only required for hot module loading in development From ede0f62cc87e06662cd36672915db5025c048d3e Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 14 Aug 2018 11:25:52 +0100 Subject: [PATCH 115/760] =?UTF-8?q?fix=20eslint=20errors=20=20=F0=9F=90=BF?= =?UTF-8?q?=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Buttons.jsx | 2 +- components/x-gift-article/src/GiftArticleWrapper.jsx | 2 +- components/x-gift-article/src/lib/api.js | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index eafa25a32..aea7f0ca4 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -34,7 +34,7 @@ export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, return ( <div className={ ButtonsClassName }> { showCopyButton && <button className={ ButtonWithGapClassNames } type="button" onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> } - <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" onClick={ shareType === SHARE_TYPE_GIFT ? emailGiftUrl : emailNonGiftUrl }>Email link</a> + <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === SHARE_TYPE_GIFT ? emailGiftUrl : emailNonGiftUrl }>Email link</a> </div> ); } diff --git a/components/x-gift-article/src/GiftArticleWrapper.jsx b/components/x-gift-article/src/GiftArticleWrapper.jsx index 6dcb051a3..fca5f7c70 100644 --- a/components/x-gift-article/src/GiftArticleWrapper.jsx +++ b/components/x-gift-article/src/GiftArticleWrapper.jsx @@ -8,6 +8,6 @@ const GiftArticleWrapper = (props) => { composedProps.composer = propsComposer; return <GiftArticle {...composedProps}/>; -};; +}; export { GiftArticleWrapper }; diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index fda4e223c..b8b77594d 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -54,7 +54,9 @@ module.exports = { isShortened = true; url = json.shortenedUrl; } - } catch (e) {} + } catch (e) { + // do nothing becasuse it just returns original url at the end + } return { url, isShortened }; }, From fe9b7bd974a7bab6980d9ca422ba7219f9f3fdac Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 14 Aug 2018 16:14:29 +0100 Subject: [PATCH 116/760] =?UTF-8?q?tidy=20up=20=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 8e8a7d802..8eea7cb65 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -17,8 +17,7 @@ "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", "@financial-times/x-interaction": "file:../x-interaction", - "classnames": "^2.2.6", - "rollup": "^0.57.1" + "classnames": "^2.2.6" }, "devDependencies": { "@financial-times/x-rollup": "file:../../packages/x-rollup", From 805d626d83d1c04bfa154482ffe0e524c50e5fbc Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 14 Aug 2018 16:32:11 +0100 Subject: [PATCH 117/760] =?UTF-8?q?create=20snapshots=20for=20new=20story?= =?UTF-8?q?=20gift=20article=20=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__snapshots__/snapshots.test.js.snap | 456 ++++++++++++++++++ 1 file changed, 456 insertions(+) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index b01a0ea55..037b3f34d 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -1,5 +1,461 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`@financial-times/x-gift-article renders a default Free article x-gift-article 1`] = ` +<div + data-x-dash-id="BaseTemplate-e036evke" +> + <form + className="GiftArticle_container__nGwU_" + name="gift-form" + > + <fieldset + className="o-forms GiftArticle_form__lC3qs" + > + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + > + Share this article (free) + </div> + <div + className="GiftArticle_url-section__Bsa7N" + data-section-id="nonGiftLink" + data-trackable="nonGiftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={false} + name="non-gift-link" + readOnly={true} + type="text" + value="https://www.ft.com/content/blahblahblah" + /> + <div + className="GiftArticle_message__2zqH2" + > + This article is currently + <span + className="GiftArticle_bold__Ys2Sp" + > + free + </span> + for anyone to read + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big js-copy-link GiftArticle_button--with-gap__34_K0" + onClick={[Function]} + type="button" + > + Copy link + </button> + <a + className="o-buttons o-buttons--primary o-buttons--big" + href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" + onClick={[Function]} + rel="noopener noreferrer" + target="_blank" + > + Email link + </a> + </div> + </div> + </fieldset> + </form> +</div> +`; + +exports[`@financial-times/x-gift-article renders a default With gift credits x-gift-article 1`] = ` +<div + data-x-dash-id="BaseTemplate-joslmcyw" +> + <form + className="GiftArticle_container__nGwU_" + name="gift-form" + > + <fieldset + className="o-forms GiftArticle_form__lC3qs" + > + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + > + Share this article (with credit) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + anyone + </span> + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + other subscribers + </span> + </label> + </div> + <div + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + <div + className="GiftArticle_message__2zqH2" + > + You have + <span + className="GiftArticle_bold__Ys2Sp" + > + gift + articles + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" + > + Create gift link + </button> + </div> + </div> + </fieldset> + <div + className="o-share o-share--inverse MobileShareButtons_container__3eAtc" + data-o-component="o-share" + > + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + > + Share on Social + </div> + <ul> + <li + className="o-share__action MobileShareButtons_button__17W-1" + data-share="facebook" + > + <a + className="o-share__icon o-share__icon--facebook MobileShareButtons_facebook__1ji2o" + data-trackable="facebook" + href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" + rel="noopener" + > + Facebook + <span + className="o-share__text" + > + (opens new window) + </span> + </a> + </li> + <li + className="o-share__action MobileShareButtons_button__17W-1" + data-share="twitter" + > + <a + className="o-share__icon o-share__icon--twitter MobileShareButtons_twitter__1QRsw" + data-trackable="twitter" + href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" + rel="noopener" + > + Twitter + <span + className="o-share__text" + > + (opens new window) + </span> + </a> + </li> + <li + className="o-share__action MobileShareButtons_button__17W-1" + data-share="linkedin" + > + <a + className="o-share__icon o-share__icon--linkedin MobileShareButtons_linkedin__1-2-Y" + data-trackable="linkedin" + href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" + rel="noopener" + > + LinkedIn + <span + className="o-share__text" + > + (opens new window) + </span> + </a> + </li> + <li + className="o-share__action MobileShareButtons_button__17W-1 o-share__action--whatsapp" + data-share="whatsapp" + > + <a + className="o-share__icon o-share__icon--whatsapp MobileShareButtons_whatsapp__16VoZ" + data-trackable="whatsapp" + href="whatsapp://send?text=Title%20Title%20Title%20Title%20-%20https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" + rel="noopener" + > + Whatsapp + <span + className="o-share__text" + > + (opens new window) + </span> + </a> + </li> + </ul> + </div> + </form> +</div> +`; + +exports[`@financial-times/x-gift-article renders a default With native share on App x-gift-article 1`] = ` +<div + data-x-dash-id="BaseTemplate-srtskctb" +> + <form + className="GiftArticle_container__nGwU_" + name="gift-form" + > + <fieldset + className="o-forms GiftArticle_form__lC3qs" + > + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + > + Share this article (on App) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + anyone + </span> + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + other subscribers + </span> + </label> + </div> + <div + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + <div + className="GiftArticle_message__2zqH2" + > + You have + <span + className="GiftArticle_bold__Ys2Sp" + > + gift + articles + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" + > + Create gift link + </button> + </div> + </div> + </fieldset> + </form> +</div> +`; + +exports[`@financial-times/x-gift-article renders a default Without gift credits x-gift-article 1`] = ` +<div + data-x-dash-id="BaseTemplate-ocf4wxkz" +> + <form + className="GiftArticle_container__nGwU_" + name="gift-form" + > + <fieldset + className="o-forms GiftArticle_form__lC3qs" + > + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + > + Share this article (without credit) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + anyone + </span> + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + other subscribers + </span> + </label> + </div> + <div + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + <div + className="GiftArticle_message__2zqH2" + > + You have + <span + className="GiftArticle_bold__Ys2Sp" + > + gift + articles + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" + > + Create gift link + </button> + </div> + </div> + </fieldset> + </form> +</div> +`; + exports[`@financial-times/x-increment renders a default Async x-increment 1`] = ` <div> <span> From 8f370e88091ad9af9130ec3ee1024c90c08d6916 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 15 Aug 2018 09:57:12 +0100 Subject: [PATCH 118/760] =?UTF-8?q?change=20a=20file=20name=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/{MobileSharebuttons.jsx => MobileShareButtonsFix.jsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/x-gift-article/src/{MobileSharebuttons.jsx => MobileShareButtonsFix.jsx} (100%) diff --git a/components/x-gift-article/src/MobileSharebuttons.jsx b/components/x-gift-article/src/MobileShareButtonsFix.jsx similarity index 100% rename from components/x-gift-article/src/MobileSharebuttons.jsx rename to components/x-gift-article/src/MobileShareButtonsFix.jsx From 53b74a758eb9c97405116b357d057f72cf27d45c Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 15 Aug 2018 09:58:12 +0100 Subject: [PATCH 119/760] =?UTF-8?q?change=20a=20file=20name=20from=20Mobil?= =?UTF-8?q?eSharebuttons=20=3D>=20MobileShareButtons=20=20=F0=9F=90=BF=20v?= =?UTF-8?q?2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/{MobileShareButtonsFix.jsx => MobileShareButtons.jsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/x-gift-article/src/{MobileShareButtonsFix.jsx => MobileShareButtons.jsx} (100%) diff --git a/components/x-gift-article/src/MobileShareButtonsFix.jsx b/components/x-gift-article/src/MobileShareButtons.jsx similarity index 100% rename from components/x-gift-article/src/MobileShareButtonsFix.jsx rename to components/x-gift-article/src/MobileShareButtons.jsx From 370171f8e0d4acb8f88407faf9d891ec48d83cc4 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 09:46:06 +0100 Subject: [PATCH 120/760] =?UTF-8?q?pass=20id=20for=20interaction=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/lib/props-composer.js | 2 ++ components/x-gift-article/stories/free-article.js | 3 ++- components/x-gift-article/stories/native-share.js | 3 ++- components/x-gift-article/stories/with-gift-credits.js | 3 ++- components/x-gift-article/stories/without-gift-credits.js | 3 ++- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 5378209e9..a0baac09c 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -4,6 +4,7 @@ import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './constants'; export class GiftArticlePropsComposer { constructor(props) { + this.id = props.id; this.isFreeArticle = props.isFreeArticle; this.articleTitle = props.articleTitle; this.articleUrl = props.articleUrl; @@ -49,6 +50,7 @@ export class GiftArticlePropsComposer { getDefault() { const fundamentalProps = { + id: this.id, title: this.title, isFreeArticle: this.isFreeArticle, isGiftUrlCreated: this.isGiftUrlCreated, diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index 4cca9e0a2..a3e138496 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -7,7 +7,8 @@ exports.data = { isFreeArticle: true, articleUrl, articleTitle: 'Title Title Title Title', - isCopySupported: true + isCopySupported: true, + id: 'base-gift-article-static-id' }; // This reference is only required for hot module loading in development diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/stories/native-share.js index 30bf41bf4..d847058f7 100644 --- a/components/x-gift-article/stories/native-share.js +++ b/components/x-gift-article/stories/native-share.js @@ -10,7 +10,8 @@ exports.data = { articleTitle: 'Title Title Title Title', articleId: 'article id', sessionId: 'session id', - nativeShare: true + nativeShare: true, + id: 'base-gift-article-static-id' }; // This reference is only required for hot module loading in development diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 7f9e142db..6e58bf631 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -11,7 +11,8 @@ exports.data = { articleId: 'article id', sessionId: 'session id', showMobileShareLinks: true, - isCopySupported: true + isCopySupported: true, + id: 'base-gift-article-static-id' }; // This reference is only required for hot module loading in development diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index 89edb5be2..44c033a32 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -7,7 +7,8 @@ exports.data = { isFreeArticle: false, articleUrl, articleTitle: 'Title Title Title Title', - isCopySupported: true + isCopySupported: true, + id: 'base-gift-article-static-id' }; // This reference is only required for hot module loading in development From 58f2137a376334642f67f62e92ca77f13d1e02e6 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 10:38:54 +0100 Subject: [PATCH 121/760] =?UTF-8?q?create=20correct=20snapshots=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/__snapshots__/snapshots.test.js.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 037b3f34d..7efb65868 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -2,7 +2,7 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-article 1`] = ` <div - data-x-dash-id="BaseTemplate-e036evke" + data-x-dash-id="base-gift-article-static-id" > <form className="GiftArticle_container__nGwU_" @@ -68,7 +68,7 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a exports[`@financial-times/x-gift-article renders a default With gift credits x-gift-article 1`] = ` <div - data-x-dash-id="BaseTemplate-joslmcyw" + data-x-dash-id="base-gift-article-static-id" > <form className="GiftArticle_container__nGwU_" @@ -254,7 +254,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g exports[`@financial-times/x-gift-article renders a default With native share on App x-gift-article 1`] = ` <div - data-x-dash-id="BaseTemplate-srtskctb" + data-x-dash-id="base-gift-article-static-id" > <form className="GiftArticle_container__nGwU_" @@ -356,7 +356,7 @@ exports[`@financial-times/x-gift-article renders a default With native share on exports[`@financial-times/x-gift-article renders a default Without gift credits x-gift-article 1`] = ` <div - data-x-dash-id="BaseTemplate-ocf4wxkz" + data-x-dash-id="base-gift-article-static-id" > <form className="GiftArticle_container__nGwU_" From 110cb07134d013ac749eceb8174ed502f65a2d41 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 11:25:52 +0100 Subject: [PATCH 122/760] =?UTF-8?q?use=20next=20renewal=20date=20in=20api?= =?UTF-8?q?=20response=20=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Form.jsx | 2 +- components/x-gift-article/src/GiftArticle.jsx | 4 ++-- components/x-gift-article/src/Message.jsx | 4 ++-- components/x-gift-article/src/UrlSection.jsx | 4 ++-- components/x-gift-article/src/lib/api.js | 5 +++-- .../src/lib/get-next-allowance-date.js | 12 ------------ .../x-gift-article/src/lib/props-composer.js | 14 ++++++-------- 7 files changed, 16 insertions(+), 29 deletions(-) delete mode 100644 components/x-gift-article/src/lib/get-next-allowance-date.js diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 43608f4b2..35e65f2d2 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -31,7 +31,7 @@ export default (props) => ( urlType={ props.urlType } giftCredits={ props.giftCredits } monthlyAllowance={ props.monthlyAllowance } - dateText={ props.dateText } + nextRenewalDateText={ props.nextRenewalDateText } mailtoUrl={ props.mailtoUrl } createGiftUrl={ props.actions.createGiftUrl } copyGiftUrl={ props.actions.copyGiftUrl } diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 4760aa363..8c29d6dfb 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -42,11 +42,11 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = }, async getAllowance() { - const { giftCredits, monthlyAllowance } = await api.getGiftArticleAllowance(); + const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); // avoid to use giftCredits >= 0 because it returns true when null and "" if (giftCredits > 0 || giftCredits === 0) { - return propsComposer.setAllowance(giftCredits, monthlyAllowance); + return propsComposer.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); } else { // TODO do something } diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index ce064136a..c32045fe5 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -5,7 +5,7 @@ import styles from './GiftArticle.css'; const messageClassName = styles.message; const boldTextClassName = styles.bold; -export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, dateText, redemptionLimit }) => { +export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, nextRenewalDateText, redemptionLimit }) => { if (isFreeArticle) { return ( @@ -20,7 +20,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month return ( <div className={ messageClassName }> You’ve used all your <span className={ boldTextClassName }>gift articles</span><br /> - You’ll get your next { monthlyAllowance } on <span className={ boldTextClassName }>{ dateText }</span> + You’ll get your next { monthlyAllowance } on <span className={ boldTextClassName }>{ nextRenewalDateText }</span> </div> ); } diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index c88f61aca..66ab7be24 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -6,7 +6,7 @@ import Buttons from './Buttons'; import styles from './GiftArticle.css'; export default ({ shareType, isGiftUrlCreated, isFreeArticle, - url, urlType, giftCredits, monthlyAllowance, dateText, + url, urlType, giftCredits, monthlyAllowance, nextRenewalDateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, emailGiftUrl, emailNonGiftUrl, redemptionLimit, showCopyButton, showNativeShareButton, shareByNativeShare }) => { @@ -33,7 +33,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, isFreeArticle={ isFreeArticle } giftCredits={ giftCredits } monthlyAllowance={ monthlyAllowance } - dateText={ dateText } + nextRenewalDateText={ nextRenewalDateText } redemptionLimit={ redemptionLimit } /> diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index b8b77594d..8b408682a 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -8,11 +8,12 @@ module.exports = { return { monthlyAllowance: json.credits.allowance, - giftCredits: json.credits.remainingCredits + giftCredits: json.credits.remainingCredits, + nextRenewalDate: json.credits.renewalDate }; } catch (e) { - return { monthlyAllowance: undefined, giftCredits: undefined }; + return { monthlyAllowance: undefined, giftCredits: undefined, nextRenewalDate: undefined }; } }, diff --git a/components/x-gift-article/src/lib/get-next-allowance-date.js b/components/x-gift-article/src/lib/get-next-allowance-date.js deleted file mode 100644 index 8b067263e..000000000 --- a/components/x-gift-article/src/lib/get-next-allowance-date.js +++ /dev/null @@ -1,12 +0,0 @@ -export default () => { - const now = new Date(); - const startOfNextMonth = new Date(Date.UTC(now.getFullYear(), now.getMonth() + 1, 1)); - const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - - return { - year: String(startOfNextMonth.getFullYear()), - month: String(startOfNextMonth.getMonth() + 1), - monthName: monthNames[startOfNextMonth.getMonth()], - day: String(startOfNextMonth.getDate()) - }; -} diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index a0baac09c..10b63f25a 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -1,7 +1,8 @@ import { createMailtoUrl } from './share-link-actions'; -import getNextAllowanceDate from './get-next-allowance-date'; import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './constants'; +const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + export class GiftArticlePropsComposer { constructor(props) { this.id = props.id; @@ -102,19 +103,16 @@ export class GiftArticlePropsComposer { }; } - setAllowance(giftCredits, monthlyAllowance) { - let dateText = undefined; + setAllowance(giftCredits, monthlyAllowance, nextRenewalDate) { + const date = new Date(nextRenewalDate); + this.nextRenewalDateText = `${ monthNames[date.getMonth()] } ${ date.getDate() }`; this.giftCredits = giftCredits; this.monthlyAllowance = monthlyAllowance; - if (giftCredits === 0) { - const nextAllowanceDate = getNextAllowanceDate(); - dateText = `${nextAllowanceDate.monthName} ${nextAllowanceDate.day}`; - } return { giftCredits: this.giftCredits, monthlyAllowance: this.monthlyAllowance, - dateText + nextRenewalDateText: this.nextRenewalDateText }; } From bb6e80b9a96d060899f6420120d48cef7b8e0021 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 12:14:56 +0100 Subject: [PATCH 123/760] =?UTF-8?q?simplify=20properties=20=20=F0=9F=90=BF?= =?UTF-8?q?=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/readme.md | 3 +-- components/x-gift-article/src/lib/props-composer.js | 7 +++++-- components/x-gift-article/stories/free-article.js | 1 - components/x-gift-article/stories/with-gift-credits.js | 1 - components/x-gift-article/stories/without-gift-credits.js | 1 - 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 5da93be0b..e1f4c260c 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -39,6 +39,5 @@ Property | Type | Required | Note `articleTitle` | String | yes | `articleId` | String | yes | Content UUID `sessionId` | String | yes | This is needed to get a gift url. -`showMobileShareLinks` | Boolean | no | This value will be `false` when `nativeShare` is `true`. -`isCopySupported` | Boolean | no | This value will be `false` when `nativeShare` is `true`. Pass whether the browser is supported the copy command or not. e.g. => `document.queryCommandSupported && document.queryCommandSupported('copy')` +`showMobileShareLinks` | Boolean | no | `nativeShare` | Boolean | no | This is a property for App to display Native Sharing. diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 10b63f25a..46b516e87 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -1,6 +1,9 @@ import { createMailtoUrl } from './share-link-actions'; import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './constants'; +const isCopySupported = typeof document !== 'undefined' + && document.queryCommandSupported + && document.queryCommandSupported('copy'); const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; export class GiftArticlePropsComposer { @@ -16,8 +19,8 @@ export class GiftArticlePropsComposer { this.giftCredits = undefined; this.monthlyAllowance = undefined; - this.showCopyButton = props.nativeShare ? false : props.isCopySupported; - this.showMobileShareLinks = props.nativeShare ? false : props.showMobileShareLinks; + this.showCopyButton = isCopySupported; + this.showMobileShareLinks = props.showMobileShareLinks; this.showNativeShareButton = props.nativeShare; this.isGiftUrlCreated = false; diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index a3e138496..ff107990d 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -7,7 +7,6 @@ exports.data = { isFreeArticle: true, articleUrl, articleTitle: 'Title Title Title Title', - isCopySupported: true, id: 'base-gift-article-static-id' }; diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 6e58bf631..2d8f1d1ca 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -11,7 +11,6 @@ exports.data = { articleId: 'article id', sessionId: 'session id', showMobileShareLinks: true, - isCopySupported: true, id: 'base-gift-article-static-id' }; diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index 44c033a32..bb32aeea8 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -7,7 +7,6 @@ exports.data = { isFreeArticle: false, articleUrl, articleTitle: 'Title Title Title Title', - isCopySupported: true, id: 'base-gift-article-static-id' }; From 60283a99bc878dad2791c44b7d42bafd9b539a33 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 12:29:28 +0100 Subject: [PATCH 124/760] =?UTF-8?q?reflect=20small=20comments=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/MobileShareButtons.jsx | 8 ++++---- components/x-gift-article/src/lib/api.js | 5 ++++- components/x-gift-article/src/lib/props-composer.js | 5 ++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/components/x-gift-article/src/MobileShareButtons.jsx b/components/x-gift-article/src/MobileShareButtons.jsx index 8b54b019b..685ab9869 100644 --- a/components/x-gift-article/src/MobileShareButtons.jsx +++ b/components/x-gift-article/src/MobileShareButtons.jsx @@ -48,22 +48,22 @@ export default ({ mobileShareLinks }) => ( <ul> <li className={ buttonClassNames } data-share="facebook"> <a className={ facebookClassNames } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook"> - Facebook<span className="o-share__text">(opens new window)</span> + Facebook <span className="o-share__text">(opens new window)</span> </a> </li> <li className={ buttonClassNames } data-share="twitter"> <a className={ twitterClassNames } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter"> - Twitter<span className="o-share__text">(opens new window)</span> + Twitter <span className="o-share__text">(opens new window)</span> </a> </li> <li className={ buttonClassNames } data-share="linkedin"> <a className={ linkedinClassNames } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin"> - LinkedIn<span className="o-share__text">(opens new window)</span> + LinkedIn <span className="o-share__text">(opens new window)</span> </a> </li> <li className={ whatsappButtonClassNames } data-share="whatsapp"> <a className={ whatsappClassNames } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp"> - Whatsapp<span className="o-share__text">(opens new window)</span> + Whatsapp <span className="o-share__text">(opens new window)</span> </a> </li> </ul> diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 8b408682a..fbaf2fdf3 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -35,7 +35,10 @@ module.exports = { throw new Error(`Failed to get gift article link: ${body.errors.join(', ')}`); } - return Object.assign({}, body, { redemptionLimit: REDEMPTION_LIMIT }); + return { + ...body, + redemptionLimit: REDEMPTION_LIMIT + }; } catch (e) { return { redemptionUrl: undefined, redemptionLimit: undefined }; diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 46b516e87..83925339b 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -68,7 +68,10 @@ export class GiftArticlePropsComposer { }; const additionalProps = this.isFreeArticle ? this.showNonGiftUrlSection() : this.showGiftUrlSection(); - return Object.assign({}, fundamentalProps, additionalProps); + return { + ...fundamentalProps, + ...additionalProps + }; } showGiftUrlSection() { From 4a92e9164a99dc0314774da37fd5297893e83c0f Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 13:50:49 +0100 Subject: [PATCH 125/760] =?UTF-8?q?update=20snapshots=20=20=F0=9F=90=BF=20?= =?UTF-8?q?v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/__snapshots__/snapshots.test.js.snap | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 7efb65868..593d9b2f8 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -43,13 +43,6 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a <div className="GiftArticle_buttons__SB7ql" > - <button - className="o-buttons o-buttons--primary o-buttons--big js-copy-link GiftArticle_button--with-gap__34_K0" - onClick={[Function]} - type="button" - > - Copy link - </button> <a className="o-buttons o-buttons--primary o-buttons--big" href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" @@ -184,7 +177,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" rel="noopener" > - Facebook + Facebook <span className="o-share__text" > @@ -202,7 +195,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" rel="noopener" > - Twitter + Twitter <span className="o-share__text" > @@ -220,7 +213,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" rel="noopener" > - LinkedIn + LinkedIn <span className="o-share__text" > @@ -238,7 +231,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g href="whatsapp://send?text=Title%20Title%20Title%20Title%20-%20https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" rel="noopener" > - Whatsapp + Whatsapp <span className="o-share__text" > From 56ed5d8a84cda6b0eb86f9c5eea67efa1edcb2f2 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 14:20:52 +0100 Subject: [PATCH 126/760] =?UTF-8?q?tidy=20up=20=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/readme.md | 10 ++++++++++ components/x-gift-article/src/GiftArticle.css | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index e1f4c260c..836b36f39 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -12,6 +12,16 @@ npm install --save @financial-times/x-gift-article [engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +## Styling + +To get correct styling, Your app should have origami components below. +[o-fonts](https://registry.origami.ft.com/components/o-fonts) +[o-buttons](https://registry.origami.ft.com/components/o-buttons) +[o-forms](https://registry.origami.ft.com/components/o-forms) +[o-loading](https://registry.origami.ft.com/components/o-loading) +[o-share](https://registry.origami.ft.com/components/o-share) +[o-message](https://registry.origami.ft.com/components/o-message) + ## Usage Component provided by this module expects a map of [gift article properties](#properties). They can be used with vanilla JavaScript or JSX (if you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css index 76f1a0e11..12240b37f 100644 --- a/components/x-gift-article/src/GiftArticle.css +++ b/components/x-gift-article/src/GiftArticle.css @@ -1,6 +1,5 @@ .container { font-family: MetricWeb,sans-serif; - max-width: 500px; } .form { From 584a53c34b439f1d5609bb82b197c5f17125026a Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 16:58:05 +0100 Subject: [PATCH 127/760] =?UTF-8?q?listen=20xDash.giftArticle.activate=20e?= =?UTF-8?q?vent=20=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/readme.md | 4 ++++ components/x-gift-article/src/GiftArticle.jsx | 22 +++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 836b36f39..9474be41c 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -36,10 +36,14 @@ const b = <GiftArticle {...props} />; const c = React.createElement(GiftArticle, props); ``` +Your app should dispatch a custom event (`xDash.giftArticle.activate`) to activate the gift article form when your app actually displays the form. +`document.body.dispatchEvent(new CustomEvent('xDash.giftArticle.activate'));` + All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. [jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ + ### Properties Property | Type | Required | Note diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 8c29d6dfb..0fa1c749e 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -99,15 +99,19 @@ const BaseTemplate = (props) => { propsComposer = props.composer; } - if (props.isFreeArticle && !hasAttemptedToShortenedNonGiftUrl) { - hasAttemptedToShortenedNonGiftUrl = true; - props.actions.getShorterNonGiftUrl(); - } - - if (!props.isFreeArticle && !hasAttemptedToGetAllowance) { - hasAttemptedToGetAllowance = true; - props.actions.getAllowance(); - } + document.body.addEventListener('xDash.giftArticle.activate', () => { + + if (props.isFreeArticle && !hasAttemptedToShortenedNonGiftUrl) { + hasAttemptedToShortenedNonGiftUrl = true; + props.actions.getShorterNonGiftUrl(); + } + + if (!props.isFreeArticle && !hasAttemptedToGetAllowance) { + hasAttemptedToGetAllowance = true; + props.actions.getAllowance(); + } + + }) return props.isLoading ? <Loading/> : <Form {...props}/>; }; From f0169c53b471a7e3962c74da4ab1e3e059c4ffc4 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 17 Aug 2018 17:14:20 +0100 Subject: [PATCH 128/760] =?UTF-8?q?delete=20attempted=20variables=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/GiftArticle.jsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 0fa1c749e..30ca7c76a 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -8,8 +8,6 @@ import api from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; import tracking from './lib/tracking'; -let hasAttemptedToShortenedNonGiftUrl = false; -let hasAttemptedToGetAllowance = false; let propsComposer; const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) => ({ @@ -100,17 +98,8 @@ const BaseTemplate = (props) => { } document.body.addEventListener('xDash.giftArticle.activate', () => { - - if (props.isFreeArticle && !hasAttemptedToShortenedNonGiftUrl) { - hasAttemptedToShortenedNonGiftUrl = true; - props.actions.getShorterNonGiftUrl(); - } - - if (!props.isFreeArticle && !hasAttemptedToGetAllowance) { - hasAttemptedToGetAllowance = true; - props.actions.getAllowance(); - } - + props.isFreeArticle ? + props.actions.getShorterNonGiftUrl() : props.actions.getAllowance(); }) return props.isLoading ? <Loading/> : <Form {...props}/>; From 380c6e6e2f981c50da6c247ec1771910ba6cae62 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 20 Aug 2018 15:29:14 +0100 Subject: [PATCH 129/760] =?UTF-8?q?modify=20readme=20=20=F0=9F=90=BF=20v2.?= =?UTF-8?q?10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 9474be41c..a4cb839be 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -28,12 +28,12 @@ Component provided by this module expects a map of [gift article properties](#pr ```jsx import React from 'react'; -import { GiftArticle } from '@financial-times/x-gift-article'; +import { GiftArticleWrapper } from '@financial-times/x-gift-article'; // A == B == C -const a = GiftArticle(props); -const b = <GiftArticle {...props} />; -const c = React.createElement(GiftArticle, props); +const a = GiftArticleWrapper(props); +const b = <GiftArticleWrapper {...props} />; +const c = React.createElement(GiftArticleWrapper, props); ``` Your app should dispatch a custom event (`xDash.giftArticle.activate`) to activate the gift article form when your app actually displays the form. From 85b64034927b5e435667cc1d0da668d6b70b915e Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 23 Aug 2018 17:46:21 +0100 Subject: [PATCH 130/760] =?UTF-8?q?changes=20after=20set=20on=20ft.com=20?= =?UTF-8?q?=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x-gift-article/src/CopyConfirmation.jsx | 10 ++- components/x-gift-article/src/Form.jsx | 64 ++++++++++--------- components/x-gift-article/src/GiftArticle.css | 10 ++- .../src/MobileShareButtons.scss | 6 +- 4 files changed, 54 insertions(+), 36 deletions(-) diff --git a/components/x-gift-article/src/CopyConfirmation.jsx b/components/x-gift-article/src/CopyConfirmation.jsx index 4d23b5fcd..0440e89a6 100644 --- a/components/x-gift-article/src/CopyConfirmation.jsx +++ b/components/x-gift-article/src/CopyConfirmation.jsx @@ -1,7 +1,15 @@ import { h } from '@financial-times/x-engine'; +import styles from './GiftArticle.css'; + +const confirmationClassNames = [ + 'o-message', + 'o-message--alert-bleed', + 'o-message--success', + styles['copy-confirmation'] +].join(' '); export default ({ hideCopyConfirmation }) => ( - <div className="o-message o-message--alert-bleed o-message--success"> + <div className={ confirmationClassNames }> <div className="o-message__container"> <div className="o-message__content"> diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 35e65f2d2..5c1ab683b 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -12,44 +12,46 @@ const formClassNames = [ ].join(' '); export default (props) => ( - <form name="gift-form" className={ styles.container }> - <fieldset className={ formClassNames }> + <div className={ styles.container }> + <form name="gift-form"> + <fieldset className={ formClassNames }> - <Title title={ props.title }/> + <Title title={ props.title }/> - { !props.isFreeArticle && <RadioButtonsSection - shareType={ props.shareType } - showGiftUrlSection={ props.actions.showGiftUrlSection } - showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> - } - - <UrlSection - shareType={ props.shareType } - isGiftUrlCreated={ props.isGiftUrlCreated } - isFreeArticle={ props.isFreeArticle } - url={ props.url } - urlType={ props.urlType } - giftCredits={ props.giftCredits } - monthlyAllowance={ props.monthlyAllowance } - nextRenewalDateText={ props.nextRenewalDateText } - mailtoUrl={ props.mailtoUrl } - createGiftUrl={ props.actions.createGiftUrl } - copyGiftUrl={ props.actions.copyGiftUrl } - copyNonGiftUrl={ props.actions.copyNonGiftUrl } - emailGiftUrl={ props.actions.emailGiftUrl } - emailNonGiftUrl={ props.actions.emailNonGiftUrl } - redemptionLimit={ props.redemptionLimit } - showCopyButton={ props.showCopyButton } - showNativeShareButton={ props.showNativeShareButton } - shareByNativeShare={ props.actions.shareByNativeShare }/> - - </fieldset> + { !props.isFreeArticle && <RadioButtonsSection + shareType={ props.shareType } + showGiftUrlSection={ props.actions.showGiftUrlSection } + showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> + } + <UrlSection + shareType={ props.shareType } + isGiftUrlCreated={ props.isGiftUrlCreated } + isFreeArticle={ props.isFreeArticle } + url={ props.url } + urlType={ props.urlType } + giftCredits={ props.giftCredits } + monthlyAllowance={ props.monthlyAllowance } + nextRenewalDateText={ props.nextRenewalDateText } + mailtoUrl={ props.mailtoUrl } + createGiftUrl={ props.actions.createGiftUrl } + copyGiftUrl={ props.actions.copyGiftUrl } + copyNonGiftUrl={ props.actions.copyNonGiftUrl } + emailGiftUrl={ props.actions.emailGiftUrl } + emailNonGiftUrl={ props.actions.emailNonGiftUrl } + redemptionLimit={ props.redemptionLimit } + showCopyButton={ props.showCopyButton } + showNativeShareButton={ props.showNativeShareButton } + shareByNativeShare={ props.actions.shareByNativeShare }/> + + </fieldset> + </form> + { props.showCopyConfirmation && <CopyConfirmation hideCopyConfirmation={ props.actions.hideCopyConfirmation }/> } { props.showShareButtons && <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> } - </form> + </div> ); diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css index 12240b37f..7fca5016b 100644 --- a/components/x-gift-article/src/GiftArticle.css +++ b/components/x-gift-article/src/GiftArticle.css @@ -5,6 +5,7 @@ .form { max-width: none; padding: 0; + margin: 0; } .bold { @@ -54,11 +55,14 @@ margin-right: 5px; } +.copy-confirmation { + margin-top: 8px; +} + .loading-spinner__container { width: 100%; height: 100%; - min-height: 200px; display: flex; - justify-content: center; - align-items: center; + justify-content: center; + align-items: center; } diff --git a/components/x-gift-article/src/MobileShareButtons.scss b/components/x-gift-article/src/MobileShareButtons.scss index 3c251bd79..4f41f7d04 100644 --- a/components/x-gift-article/src/MobileShareButtons.scss +++ b/components/x-gift-article/src/MobileShareButtons.scss @@ -21,6 +21,10 @@ align-items: center; justify-content: center; text-decoration: none; + font-family: MetricWeb,sans-serif; + font-size: 16px; + font-weight: 600; + &:before { position: absolute; left: 0; @@ -33,7 +37,7 @@ } .container { - margin-top: 16px; + margin-top: 36px; width: 100%; ul { padding-left: 0; From cc82d938f8f07ec10b06fdcb0201ea59ba924810 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 28 Aug 2018 14:37:57 +0100 Subject: [PATCH 131/760] =?UTF-8?q?update=20snapshots=20=20=F0=9F=90=BF=20?= =?UTF-8?q?v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__snapshots__/snapshots.test.js.snap | 600 +++++++++--------- 1 file changed, 306 insertions(+), 294 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 593d9b2f8..8ff3d57f6 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -4,58 +4,61 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a <div data-x-dash-id="base-gift-article-static-id" > - <form + <div className="GiftArticle_container__nGwU_" - name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__lC3qs" + <form + name="gift-form" > - <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + <fieldset + className="o-forms GiftArticle_form__lC3qs" > - Share this article (free) - </div> - <div - className="GiftArticle_url-section__Bsa7N" - data-section-id="nonGiftLink" - data-trackable="nonGiftLink" - > - <input - className="o-forms__text GiftArticle_url__17SKH" - disabled={false} - name="non-gift-link" - readOnly={true} - type="text" - value="https://www.ft.com/content/blahblahblah" - /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > - This article is currently - <span - className="GiftArticle_bold__Ys2Sp" - > - free - </span> - for anyone to read + Share this article (free) </div> <div - className="GiftArticle_buttons__SB7ql" - > - <a - className="o-buttons o-buttons--primary o-buttons--big" - href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" - onClick={[Function]} - rel="noopener noreferrer" - target="_blank" + className="GiftArticle_url-section__Bsa7N" + data-section-id="nonGiftLink" + data-trackable="nonGiftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={false} + name="non-gift-link" + readOnly={true} + type="text" + value="https://www.ft.com/content/blahblahblah" + /> + <div + className="GiftArticle_message__2zqH2" > - Email link - </a> + This article is currently + <span + className="GiftArticle_bold__Ys2Sp" + > + free + </span> + for anyone to read + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <a + className="o-buttons o-buttons--primary o-buttons--big" + href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" + onClick={[Function]} + rel="noopener noreferrer" + target="_blank" + > + Email link + </a> + </div> </div> - </div> - </fieldset> - </form> + </fieldset> + </form> + </div> </div> `; @@ -63,100 +66,103 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g <div data-x-dash-id="base-gift-article-static-id" > - <form + <div className="GiftArticle_container__nGwU_" - name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__lC3qs" + <form + name="gift-form" > - <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + <fieldset + className="o-forms GiftArticle_form__lC3qs" > - Share this article (with credit) - </div> - <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" - > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <label - className="o-forms__label" - htmlFor="giftLink" - > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - anyone - </span> - </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <label - className="o-forms__label" - htmlFor="nonGiftLink" - > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - other subscribers - </span> - </label> - </div> - <div - className="GiftArticle_url-section__Bsa7N" - data-section-id="giftLink" - data-trackable="giftLink" - > - <input - className="o-forms__text GiftArticle_url__17SKH" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > - You have - <span - className="GiftArticle_bold__Ys2Sp" + Share this article (with credit) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" > - gift - articles - </span> - left this month + with + <span + className="GiftArticle_bold__Ys2Sp" + > + anyone + </span> + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + other subscribers + </span> + </label> </div> <div - className="GiftArticle_buttons__SB7ql" - > - <button - className="o-buttons o-buttons--primary o-buttons--big" - onClick={[Function]} - type="button" + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + <div + className="GiftArticle_message__2zqH2" > - Create gift link - </button> + You have + <span + className="GiftArticle_bold__Ys2Sp" + > + gift + articles + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" + > + Create gift link + </button> + </div> </div> - </div> - </fieldset> + </fieldset> + </form> <div className="o-share o-share--inverse MobileShareButtons_container__3eAtc" data-o-component="o-share" @@ -241,7 +247,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g </li> </ul> </div> - </form> + </div> </div> `; @@ -249,101 +255,104 @@ exports[`@financial-times/x-gift-article renders a default With native share on <div data-x-dash-id="base-gift-article-static-id" > - <form + <div className="GiftArticle_container__nGwU_" - name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__lC3qs" + <form + name="gift-form" > - <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + <fieldset + className="o-forms GiftArticle_form__lC3qs" > - Share this article (on App) - </div> - <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" - > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <label - className="o-forms__label" - htmlFor="giftLink" - > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - anyone - </span> - </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <label - className="o-forms__label" - htmlFor="nonGiftLink" - > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - other subscribers - </span> - </label> - </div> - <div - className="GiftArticle_url-section__Bsa7N" - data-section-id="giftLink" - data-trackable="giftLink" - > - <input - className="o-forms__text GiftArticle_url__17SKH" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > - You have - <span - className="GiftArticle_bold__Ys2Sp" + Share this article (on App) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" > - gift - articles - </span> - left this month + with + <span + className="GiftArticle_bold__Ys2Sp" + > + anyone + </span> + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + other subscribers + </span> + </label> </div> <div - className="GiftArticle_buttons__SB7ql" - > - <button - className="o-buttons o-buttons--primary o-buttons--big" - onClick={[Function]} - type="button" + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + <div + className="GiftArticle_message__2zqH2" + > + You have + <span + className="GiftArticle_bold__Ys2Sp" + > + gift + articles + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" > - Create gift link - </button> + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" + > + Create gift link + </button> + </div> </div> - </div> - </fieldset> - </form> + </fieldset> + </form> + </div> </div> `; @@ -351,101 +360,104 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits <div data-x-dash-id="base-gift-article-static-id" > - <form + <div className="GiftArticle_container__nGwU_" - name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__lC3qs" + <form + name="gift-form" > - <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + <fieldset + className="o-forms GiftArticle_form__lC3qs" > - Share this article (without credit) - </div> - <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" - > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <label - className="o-forms__label" - htmlFor="giftLink" - > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - anyone - </span> - </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <label - className="o-forms__label" - htmlFor="nonGiftLink" - > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - other subscribers - </span> - </label> - </div> - <div - className="GiftArticle_url-section__Bsa7N" - data-section-id="giftLink" - data-trackable="giftLink" - > - <input - className="o-forms__text GiftArticle_url__17SKH" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > - You have - <span - className="GiftArticle_bold__Ys2Sp" + Share this article (without credit) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + anyone + </span> + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" > - gift - articles - </span> - left this month + with + <span + className="GiftArticle_bold__Ys2Sp" + > + other subscribers + </span> + </label> </div> <div - className="GiftArticle_buttons__SB7ql" - > - <button - className="o-buttons o-buttons--primary o-buttons--big" - onClick={[Function]} - type="button" + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + <div + className="GiftArticle_message__2zqH2" > - Create gift link - </button> + You have + <span + className="GiftArticle_bold__Ys2Sp" + > + gift + articles + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" + > + Create gift link + </button> + </div> </div> - </div> - </fieldset> - </form> + </fieldset> + </form> + </div> </div> `; From fc62384514fa61e17f41cb66fcdda0feccb59420 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 28 Aug 2018 17:28:28 +0100 Subject: [PATCH 132/760] =?UTF-8?q?fix=20loading=20spinner=20position=20?= =?UTF-8?q?=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/GiftArticle.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css index 7fca5016b..ad5e795ac 100644 --- a/components/x-gift-article/src/GiftArticle.css +++ b/components/x-gift-article/src/GiftArticle.css @@ -60,9 +60,12 @@ } .loading-spinner__container { - width: 100%; - height: 100%; - display: flex; justify-content: center; align-items: center; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; } From 463921d2c2970e31b79aa46ce5439faf97c33118 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 28 Aug 2018 17:32:26 +0100 Subject: [PATCH 133/760] =?UTF-8?q?fix=20loading=20spinner=20position=20?= =?UTF-8?q?=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/GiftArticle.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.css index ad5e795ac..4335be64f 100644 --- a/components/x-gift-article/src/GiftArticle.css +++ b/components/x-gift-article/src/GiftArticle.css @@ -60,6 +60,7 @@ } .loading-spinner__container { + display: flex; justify-content: center; align-items: center; position: absolute; @@ -67,5 +68,4 @@ right: 0; bottom: 0; left: 0; - display: flex; } From b0b0fa16c873a243255aa4d32b356491dcdc7445 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Wed, 12 Sep 2018 10:37:38 +0100 Subject: [PATCH 134/760] =?UTF-8?q?use=20composer=20passed=20into=20action?= =?UTF-8?q?s=20to=20avoid=20singleton=20=20=F0=9F=90=BF=20v2.10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/GiftArticle.jsx | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 30ca7c76a..7749af4e1 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -8,22 +8,20 @@ import api from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; import tracking from './lib/tracking'; -let propsComposer; - -const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) => ({ +const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId, composer }) => ({ showGiftUrlSection() { - return propsComposer.showGiftUrlSection(); + return composer.showGiftUrlSection(); }, async showNonGiftUrlSection() { - if (propsComposer.isNonGiftUrlShortened) { - return propsComposer.showNonGiftUrlSection(); + if (composer.isNonGiftUrlShortened) { + return composer.showNonGiftUrlSection(); } else { const { url, isShortened } = await api.getShorterUrl(articleUrl); if (isShortened) { - propsComposer.setShortenedNonGiftUrl(url); + composer.setShortenedNonGiftUrl(url); } - return propsComposer.showNonGiftUrlSection(); + return composer.showNonGiftUrlSection(); } }, @@ -33,7 +31,7 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = if (redemptionUrl) { const { url, isShortened } = await api.getShorterUrl(redemptionUrl); tracking.createGiftLink(url, redemptionUrl); - return propsComposer.setGiftUrl(url, redemptionLimit, isShortened); + return composer.setGiftUrl(url, redemptionLimit, isShortened); } else { // TODO do something } @@ -44,7 +42,7 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = // avoid to use giftCredits >= 0 because it returns true when null and "" if (giftCredits > 0 || giftCredits === 0) { - return propsComposer.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); + return composer.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); } else { // TODO do something } @@ -54,49 +52,44 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId }) = const { url, isShortened } = await api.getShorterUrl(articleUrl); if (isShortened) { - return propsComposer.setShortenedNonGiftUrl(url); + return composer.setShortenedNonGiftUrl(url); } }, copyGiftUrl() { - const giftUrl = propsComposer.urls.gift; + const giftUrl = composer.urls.gift; copyToClipboard(giftUrl); tracking.copyLink('giftLink', giftUrl); - return propsComposer.showCopyConfirmation(); + return composer.showCopyConfirmation(); }, copyNonGiftUrl() { - const nonGiftUrl = propsComposer.urls.nonGift; + const nonGiftUrl = composer.urls.nonGift; copyToClipboard(nonGiftUrl); tracking.copyLink('nonGiftLink', nonGiftUrl); - return propsComposer.showCopyConfirmation(); + return composer.showCopyConfirmation(); }, emailGiftUrl() { - tracking.emailLink('giftLink', propsComposer.urls.gift); + tracking.emailLink('giftLink', composer.urls.gift); }, emailNonGiftUrl() { - tracking.emailLink('nonGiftLink', propsComposer.urls.nonGift); + tracking.emailLink('nonGiftLink', composer.urls.nonGift); }, hideCopyConfirmation() { - return propsComposer.hideCopyConfirmation(); + return composer.hideCopyConfirmation(); }, shareByNativeShare() { // TODO display native share } - })); const BaseTemplate = (props) => { - if (!propsComposer) { - propsComposer = props.composer; - } - document.body.addEventListener('xDash.giftArticle.activate', () => { props.isFreeArticle ? props.actions.getShorterNonGiftUrl() : props.actions.getAllowance(); From 17ff2fe914dedfccade8c2456f2a4bc2ef600e00 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 18 Sep 2018 14:23:44 +0100 Subject: [PATCH 135/760] =?UTF-8?q?improve=20wording=20=20=F0=9F=90=BF=20v?= =?UTF-8?q?2.10.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/x-gift-article/src/Message.jsx | 4 ++-- components/x-gift-article/src/RadioButtonsSection.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index c32045fe5..04a3c1eb3 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -19,7 +19,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month if (giftCredits === 0) { return ( <div className={ messageClassName }> - You’ve used all your <span className={ boldTextClassName }>gift articles</span><br /> + You’ve used all your <span className={ boldTextClassName }>gift article credits</span><br /> You’ll get your next { monthlyAllowance } on <span className={ boldTextClassName }>{ nextRenewalDateText }</span> </div> ); @@ -35,7 +35,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month return ( <div className={ messageClassName }> - You have <span className={ boldTextClassName }>{ giftCredits } gift { giftCredits === 1 ? 'article' : 'articles' }</span> left this month + You have <span className={ boldTextClassName }>{ giftCredits } gift article { giftCredits === 1 ? 'credit' : 'credits' }</span> left this month </div>); } diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 76fd28894..f103b0d05 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -22,7 +22,7 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( onChange={ showGiftUrlSection }/> <label htmlFor="giftLink" className="o-forms__label"> - with <span className={ boldTextClassName }>anyone</span> + with <span className={ boldTextClassName }>anyone</span> (uses 1 gift credit) </label> <input @@ -35,7 +35,7 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( onChange={ showNonGiftUrlSection }/> <label htmlFor="nonGiftLink" className="o-forms__label"> - with <span className={ boldTextClassName }>other subscribers</span> + with <span className={ boldTextClassName }>other FT subscribers</span> </label> </div> From de59e13c94ee31739395a614ccc33f37a2f3e4ee Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 18 Sep 2018 15:23:17 +0100 Subject: [PATCH 136/760] =?UTF-8?q?update=20snapshot=20tests=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__snapshots__/snapshots.test.js.snap | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 8ff3d57f6..e93b13048 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -102,6 +102,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g > anyone </span> + (uses 1 gift credit) </label> <input checked={false} @@ -120,7 +121,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g <span className="GiftArticle_bold__Ys2Sp" > - other subscribers + other FT subscribers </span> </label> </div> @@ -144,8 +145,8 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g <span className="GiftArticle_bold__Ys2Sp" > - gift - articles + gift article + credits </span> left this month </div> @@ -291,6 +292,7 @@ exports[`@financial-times/x-gift-article renders a default With native share on > anyone </span> + (uses 1 gift credit) </label> <input checked={false} @@ -309,7 +311,7 @@ exports[`@financial-times/x-gift-article renders a default With native share on <span className="GiftArticle_bold__Ys2Sp" > - other subscribers + other FT subscribers </span> </label> </div> @@ -333,8 +335,8 @@ exports[`@financial-times/x-gift-article renders a default With native share on <span className="GiftArticle_bold__Ys2Sp" > - gift - articles + gift article + credits </span> left this month </div> @@ -396,6 +398,7 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits > anyone </span> + (uses 1 gift credit) </label> <input checked={false} @@ -414,7 +417,7 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits <span className="GiftArticle_bold__Ys2Sp" > - other subscribers + other FT subscribers </span> </label> </div> @@ -438,8 +441,8 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits <span className="GiftArticle_bold__Ys2Sp" > - gift - articles + gift article + credits </span> left this month </div> From 50a5de54c0a8d1d5945c237c4f7c7fdb1977f593 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 24 Sep 2018 10:42:30 +0100 Subject: [PATCH 137/760] rebase master --- components/x-gift-article/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 8eea7cb65..8eb01bfba 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -11,7 +11,9 @@ "build": "node rollup.js", "start": "node rollup.js --watch" }, - "keywords": ["x-dash"], + "keywords": [ + "x-dash" + ], "author": "", "license": "ISC", "dependencies": { From ec55af549a051e0a0172dde09dba836d848cbd3a Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 24 Sep 2018 10:58:34 +0100 Subject: [PATCH 138/760] update snapshots --- .../__snapshots__/snapshots.test.js.snap | 770 +++++++++--------- 1 file changed, 377 insertions(+), 393 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index e93b13048..3a42a0089 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -2,465 +2,449 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-article 1`] = ` <div - data-x-dash-id="base-gift-article-static-id" + className="GiftArticle_container__nGwU_" > - <div - className="GiftArticle_container__nGwU_" + <form + name="gift-form" > - <form - name="gift-form" + <fieldset + className="o-forms GiftArticle_form__lC3qs" > - <fieldset - className="o-forms GiftArticle_form__lC3qs" + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > + Share this article (free) + </div> + <div + className="GiftArticle_url-section__Bsa7N" + data-section-id="nonGiftLink" + data-trackable="nonGiftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={false} + name="non-gift-link" + readOnly={true} + type="text" + value="https://www.ft.com/content/blahblahblah" + /> <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + className="GiftArticle_message__2zqH2" > - Share this article (free) + This article is currently + <span + className="GiftArticle_bold__Ys2Sp" + > + free + </span> + for anyone to read </div> <div - className="GiftArticle_url-section__Bsa7N" - data-section-id="nonGiftLink" - data-trackable="nonGiftLink" - > - <input - className="o-forms__text GiftArticle_url__17SKH" - disabled={false} - name="non-gift-link" - readOnly={true} - type="text" - value="https://www.ft.com/content/blahblahblah" - /> - <div - className="GiftArticle_message__2zqH2" - > - This article is currently - <span - className="GiftArticle_bold__Ys2Sp" - > - free - </span> - for anyone to read - </div> - <div - className="GiftArticle_buttons__SB7ql" + className="GiftArticle_buttons__SB7ql" + > + <a + className="o-buttons o-buttons--primary o-buttons--big" + href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" + onClick={[Function]} + rel="noopener noreferrer" + target="_blank" > - <a - className="o-buttons o-buttons--primary o-buttons--big" - href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" - onClick={[Function]} - rel="noopener noreferrer" - target="_blank" - > - Email link - </a> - </div> + Email link + </a> </div> - </fieldset> - </form> - </div> + </div> + </fieldset> + </form> </div> `; exports[`@financial-times/x-gift-article renders a default With gift credits x-gift-article 1`] = ` <div - data-x-dash-id="base-gift-article-static-id" + className="GiftArticle_container__nGwU_" > - <div - className="GiftArticle_container__nGwU_" + <form + name="gift-form" > - <form - name="gift-form" + <fieldset + className="o-forms GiftArticle_form__lC3qs" > - <fieldset - className="o-forms GiftArticle_form__lC3qs" + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > - <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + Share this article (with credit) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" > - Share this article (with credit) - </div> - <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" - > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <label - className="o-forms__label" - htmlFor="giftLink" + with + <span + className="GiftArticle_bold__Ys2Sp" > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - anyone - </span> - (uses 1 gift credit) - </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <label - className="o-forms__label" - htmlFor="nonGiftLink" + anyone + </span> + (uses 1 gift credit) + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - other FT subscribers - </span> - </label> - </div> + other FT subscribers + </span> + </label> + </div> + <div + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> <div - className="GiftArticle_url-section__Bsa7N" - data-section-id="giftLink" - data-trackable="giftLink" - > - <input - className="o-forms__text GiftArticle_url__17SKH" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> - <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_message__2zqH2" + > + You have + <span + className="GiftArticle_bold__Ys2Sp" > - You have - <span - className="GiftArticle_bold__Ys2Sp" - > - gift article - credits - </span> - left this month - </div> - <div - className="GiftArticle_buttons__SB7ql" + gift article + credits + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" > - <button - className="o-buttons o-buttons--primary o-buttons--big" - onClick={[Function]} - type="button" - > - Create gift link - </button> - </div> + Create gift link + </button> </div> - </fieldset> - </form> + </div> + </fieldset> + </form> + <div + className="o-share o-share--inverse MobileShareButtons_container__3eAtc" + data-o-component="o-share" + > <div - className="o-share o-share--inverse MobileShareButtons_container__3eAtc" - data-o-component="o-share" + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > - <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + Share on Social + </div> + <ul> + <li + className="o-share__action MobileShareButtons_button__17W-1" + data-share="facebook" > - Share on Social - </div> - <ul> - <li - className="o-share__action MobileShareButtons_button__17W-1" - data-share="facebook" - > - <a - className="o-share__icon o-share__icon--facebook MobileShareButtons_facebook__1ji2o" - data-trackable="facebook" - href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" - rel="noopener" + <a + className="o-share__icon o-share__icon--facebook MobileShareButtons_facebook__1ji2o" + data-trackable="facebook" + href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" + rel="noopener" + > + Facebook + <span + className="o-share__text" > - Facebook - <span - className="o-share__text" - > - (opens new window) - </span> - </a> - </li> - <li - className="o-share__action MobileShareButtons_button__17W-1" - data-share="twitter" - > - <a - className="o-share__icon o-share__icon--twitter MobileShareButtons_twitter__1QRsw" - data-trackable="twitter" - href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" - rel="noopener" + (opens new window) + </span> + </a> + </li> + <li + className="o-share__action MobileShareButtons_button__17W-1" + data-share="twitter" + > + <a + className="o-share__icon o-share__icon--twitter MobileShareButtons_twitter__1QRsw" + data-trackable="twitter" + href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" + rel="noopener" + > + Twitter + <span + className="o-share__text" > - Twitter - <span - className="o-share__text" - > - (opens new window) - </span> - </a> - </li> - <li - className="o-share__action MobileShareButtons_button__17W-1" - data-share="linkedin" - > - <a - className="o-share__icon o-share__icon--linkedin MobileShareButtons_linkedin__1-2-Y" - data-trackable="linkedin" - href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" - rel="noopener" + (opens new window) + </span> + </a> + </li> + <li + className="o-share__action MobileShareButtons_button__17W-1" + data-share="linkedin" + > + <a + className="o-share__icon o-share__icon--linkedin MobileShareButtons_linkedin__1-2-Y" + data-trackable="linkedin" + href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" + rel="noopener" + > + LinkedIn + <span + className="o-share__text" > - LinkedIn - <span - className="o-share__text" - > - (opens new window) - </span> - </a> - </li> - <li - className="o-share__action MobileShareButtons_button__17W-1 o-share__action--whatsapp" - data-share="whatsapp" - > - <a - className="o-share__icon o-share__icon--whatsapp MobileShareButtons_whatsapp__16VoZ" - data-trackable="whatsapp" - href="whatsapp://send?text=Title%20Title%20Title%20Title%20-%20https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" - rel="noopener" + (opens new window) + </span> + </a> + </li> + <li + className="o-share__action MobileShareButtons_button__17W-1 o-share__action--whatsapp" + data-share="whatsapp" + > + <a + className="o-share__icon o-share__icon--whatsapp MobileShareButtons_whatsapp__16VoZ" + data-trackable="whatsapp" + href="whatsapp://send?text=Title%20Title%20Title%20Title%20-%20https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" + rel="noopener" + > + Whatsapp + <span + className="o-share__text" > - Whatsapp - <span - className="o-share__text" - > - (opens new window) - </span> - </a> - </li> - </ul> - </div> + (opens new window) + </span> + </a> + </li> + </ul> </div> </div> `; exports[`@financial-times/x-gift-article renders a default With native share on App x-gift-article 1`] = ` <div - data-x-dash-id="base-gift-article-static-id" + className="GiftArticle_container__nGwU_" > - <div - className="GiftArticle_container__nGwU_" + <form + name="gift-form" > - <form - name="gift-form" + <fieldset + className="o-forms GiftArticle_form__lC3qs" > - <fieldset - className="o-forms GiftArticle_form__lC3qs" + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > - <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + Share this article (on App) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" > - Share this article (on App) - </div> - <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" - > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <label - className="o-forms__label" - htmlFor="giftLink" + with + <span + className="GiftArticle_bold__Ys2Sp" > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - anyone - </span> - (uses 1 gift credit) - </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <label - className="o-forms__label" - htmlFor="nonGiftLink" + anyone + </span> + (uses 1 gift credit) + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - other FT subscribers - </span> - </label> - </div> + other FT subscribers + </span> + </label> + </div> + <div + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> <div - className="GiftArticle_url-section__Bsa7N" - data-section-id="giftLink" - data-trackable="giftLink" - > - <input - className="o-forms__text GiftArticle_url__17SKH" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> - <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_message__2zqH2" + > + You have + <span + className="GiftArticle_bold__Ys2Sp" > - You have - <span - className="GiftArticle_bold__Ys2Sp" - > - gift article - credits - </span> - left this month - </div> - <div - className="GiftArticle_buttons__SB7ql" + gift article + credits + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" > - <button - className="o-buttons o-buttons--primary o-buttons--big" - onClick={[Function]} - type="button" - > - Create gift link - </button> - </div> + Create gift link + </button> </div> - </fieldset> - </form> - </div> + </div> + </fieldset> + </form> </div> `; exports[`@financial-times/x-gift-article renders a default Without gift credits x-gift-article 1`] = ` <div - data-x-dash-id="base-gift-article-static-id" + className="GiftArticle_container__nGwU_" > - <div - className="GiftArticle_container__nGwU_" + <form + name="gift-form" > - <form - name="gift-form" + <fieldset + className="o-forms GiftArticle_form__lC3qs" > - <fieldset - className="o-forms GiftArticle_form__lC3qs" + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" > - <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + Share this article (without credit) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" > - Share this article (without credit) - </div> - <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" - > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <label - className="o-forms__label" - htmlFor="giftLink" + with + <span + className="GiftArticle_bold__Ys2Sp" > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - anyone - </span> - (uses 1 gift credit) - </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <label - className="o-forms__label" - htmlFor="nonGiftLink" + anyone + </span> + (uses 1 gift credit) + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" > - with - <span - className="GiftArticle_bold__Ys2Sp" - > - other FT subscribers - </span> - </label> - </div> + other FT subscribers + </span> + </label> + </div> + <div + className="GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> <div - className="GiftArticle_url-section__Bsa7N" - data-section-id="giftLink" - data-trackable="giftLink" - > - <input - className="o-forms__text GiftArticle_url__17SKH" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> - <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_message__2zqH2" + > + You have + <span + className="GiftArticle_bold__Ys2Sp" > - You have - <span - className="GiftArticle_bold__Ys2Sp" - > - gift article - credits - </span> - left this month - </div> - <div - className="GiftArticle_buttons__SB7ql" + gift article + credits + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + onClick={[Function]} + type="button" > - <button - className="o-buttons o-buttons--primary o-buttons--big" - onClick={[Function]} - type="button" - > - Create gift link - </button> - </div> + Create gift link + </button> </div> - </fieldset> - </form> - </div> + </div> + </fieldset> + </form> </div> `; From 4bc3e31f5bf1a9f9611f4ae7fc5beaaedc27212d Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 25 Sep 2018 10:24:12 +0100 Subject: [PATCH 139/760] fix a copy function bug on IE11 --- components/x-gift-article/src/GiftArticle.jsx | 4 +- components/x-gift-article/src/Url.jsx | 1 + .../src/lib/share-link-actions.js | 45 ++++++++++--------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 7749af4e1..b9e892ea2 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -58,7 +58,7 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId, com copyGiftUrl() { const giftUrl = composer.urls.gift; - copyToClipboard(giftUrl); + copyToClipboard(); tracking.copyLink('giftLink', giftUrl); return composer.showCopyConfirmation(); @@ -66,7 +66,7 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId, com copyNonGiftUrl() { const nonGiftUrl = composer.urls.nonGift; - copyToClipboard(nonGiftUrl); + copyToClipboard(); tracking.copyLink('nonGiftLink', nonGiftUrl); return composer.showCopyConfirmation(); diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 83fc0162b..4f7564319 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -4,6 +4,7 @@ import styles from './GiftArticle.css'; const urlClassNames = [ 'o-forms__text', + 'js-gift-article__copy-target', styles.url ].join(' '); diff --git a/components/x-gift-article/src/lib/share-link-actions.js b/components/x-gift-article/src/lib/share-link-actions.js index 43ecd26e8..ce5a1dc4c 100644 --- a/components/x-gift-article/src/lib/share-link-actions.js +++ b/components/x-gift-article/src/lib/share-link-actions.js @@ -6,29 +6,30 @@ function createMailtoUrl (articleTitle, shareUrl) { } -function copyToClipboard (copyText) { - - const selected = - document.getSelection().rangeCount > 0 // Check if there is any content selected previously - ? document.getSelection().getRangeAt(0) // Store selection if found - : false; // Mark as false to know no selection existed before - - const el = document.createElement('textarea'); // Create a <textarea> element - el.value = copyText; // Set its value to the string that you want copied - el.setAttribute('readonly', ''); // Make it readonly to be tamper-proof - el.style.position = 'absolute'; - el.style.left = '-9999px'; // Move outside the screen to make it invisible - document.body.appendChild(el); // Append the <textarea> element to the HTML document - - el.select(); // Select the <textarea> content - document.execCommand('copy'); // Copy - document.body.removeChild(el); // Remove the <textarea> element - - if (selected) { // If a selection existed before copying - document.getSelection().removeAllRanges(); // Unselect everything on the HTML document - document.getSelection().addRange(selected); // Restore the original selection +function copyToClipboard (targetClass = '.js-gift-article__copy-target') { + const inputEl = document.querySelector(targetClass); + const oldContentEditable = inputEl.contentEditable; + const oldReadOnly = inputEl.readOnly; + const range = document.createRange(); + + inputEl.contenteditable = true; + inputEl.readonly = false; + inputEl.focus(); + range.selectNodeContents(inputEl); + + const selection = window.getSelection(); + + try { + selection.removeAllRanges(); + selection.addRange(range); + inputEl.setSelectionRange(0, 999999); + } catch (err) { + inputEl.select(); // IE11 etc. } - + inputEl.contentEditable = oldContentEditable; + inputEl.readOnly = oldReadOnly; + document.execCommand('copy'); + inputEl.blur(); } module.exports = { From 83daa74eb6f687e50f772e9e710c76fc06f59e07 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 25 Sep 2018 10:29:23 +0100 Subject: [PATCH 140/760] update snapshots --- __tests__/__snapshots__/snapshots.test.js.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 3a42a0089..7fc9675f8 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -21,7 +21,7 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a data-trackable="nonGiftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text js-gift-article__copy-target GiftArticle_url__17SKH" disabled={false} name="non-gift-link" readOnly={true} @@ -124,7 +124,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g data-trackable="giftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text js-gift-article__copy-target GiftArticle_url__17SKH" disabled={true} name="example-gift-link" readOnly={true} @@ -310,7 +310,7 @@ exports[`@financial-times/x-gift-article renders a default With native share on data-trackable="giftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text js-gift-article__copy-target GiftArticle_url__17SKH" disabled={true} name="example-gift-link" readOnly={true} @@ -412,7 +412,7 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits data-trackable="giftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text js-gift-article__copy-target GiftArticle_url__17SKH" disabled={true} name="example-gift-link" readOnly={true} From 77179c8e30cfb0d914fb1d195f4ba4c1505cd2e7 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 25 Sep 2018 14:30:44 +0100 Subject: [PATCH 141/760] set a specific copy target for multiple instances' situation --- components/x-gift-article/src/GiftArticle.jsx | 8 ++++---- components/x-gift-article/src/Url.jsx | 1 - components/x-gift-article/src/UrlSection.jsx | 7 ++++++- components/x-gift-article/src/lib/share-link-actions.js | 6 ++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index b9e892ea2..b23e9a755 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -56,17 +56,17 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId, com } }, - copyGiftUrl() { + copyGiftUrl(event) { const giftUrl = composer.urls.gift; - copyToClipboard(); + copyToClipboard(event); tracking.copyLink('giftLink', giftUrl); return composer.showCopyConfirmation(); }, - copyNonGiftUrl() { + copyNonGiftUrl(event) { const nonGiftUrl = composer.urls.nonGift; - copyToClipboard(); + copyToClipboard(event); tracking.copyLink('nonGiftLink', nonGiftUrl); return composer.showCopyConfirmation(); diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 4f7564319..83fc0162b 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -4,7 +4,6 @@ import styles from './GiftArticle.css'; const urlClassNames = [ 'o-forms__text', - 'js-gift-article__copy-target', styles.url ].join(' '); diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 66ab7be24..ae1b03533 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -5,6 +5,11 @@ import Message from './Message'; import Buttons from './Buttons'; import styles from './GiftArticle.css'; +const urlSectionClassNames = [ + 'js-gift-article__url-section', + styles['url-section'] +].join(' '); + export default ({ shareType, isGiftUrlCreated, isFreeArticle, url, urlType, giftCredits, monthlyAllowance, nextRenewalDateText, mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, @@ -16,7 +21,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, return ( <div - className={ styles['url-section'] } + className={ urlSectionClassNames } data-section-id={ shareType + 'Link' } data-trackable={ shareType + 'Link' }> diff --git a/components/x-gift-article/src/lib/share-link-actions.js b/components/x-gift-article/src/lib/share-link-actions.js index ce5a1dc4c..10c30ae08 100644 --- a/components/x-gift-article/src/lib/share-link-actions.js +++ b/components/x-gift-article/src/lib/share-link-actions.js @@ -6,8 +6,10 @@ function createMailtoUrl (articleTitle, shareUrl) { } -function copyToClipboard (targetClass = '.js-gift-article__copy-target') { - const inputEl = document.querySelector(targetClass); +function copyToClipboard (event) { + + const urlSection = event.target.closest('fieldset > .js-gift-article__url-section'); + const inputEl = urlSection.querySelector('input'); const oldContentEditable = inputEl.contentEditable; const oldReadOnly = inputEl.readOnly; const range = document.createRange(); From 14eed85a46feef4498deb78c5ded97cdf69047c2 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 25 Sep 2018 14:32:20 +0100 Subject: [PATCH 142/760] update snapshots --- __tests__/__snapshots__/snapshots.test.js.snap | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 7fc9675f8..3dc384fb8 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -16,12 +16,12 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a Share this article (free) </div> <div - className="GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" data-section-id="nonGiftLink" data-trackable="nonGiftLink" > <input - className="o-forms__text js-gift-article__copy-target GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__17SKH" disabled={false} name="non-gift-link" readOnly={true} @@ -119,12 +119,12 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g </label> </div> <div - className="GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" data-section-id="giftLink" data-trackable="giftLink" > <input - className="o-forms__text js-gift-article__copy-target GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__17SKH" disabled={true} name="example-gift-link" readOnly={true} @@ -305,12 +305,12 @@ exports[`@financial-times/x-gift-article renders a default With native share on </label> </div> <div - className="GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" data-section-id="giftLink" data-trackable="giftLink" > <input - className="o-forms__text js-gift-article__copy-target GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__17SKH" disabled={true} name="example-gift-link" readOnly={true} @@ -407,12 +407,12 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits </label> </div> <div - className="GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" data-section-id="giftLink" data-trackable="giftLink" > <input - className="o-forms__text js-gift-article__copy-target GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__17SKH" disabled={true} name="example-gift-link" readOnly={true} From 955b3cc06a945e5655bdfb37ea8f1aea0d6255bd Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 26 Sep 2018 17:15:15 +0100 Subject: [PATCH 143/760] delete combinator in closest --- components/x-gift-article/src/lib/share-link-actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/src/lib/share-link-actions.js b/components/x-gift-article/src/lib/share-link-actions.js index 10c30ae08..0992efc5b 100644 --- a/components/x-gift-article/src/lib/share-link-actions.js +++ b/components/x-gift-article/src/lib/share-link-actions.js @@ -8,7 +8,7 @@ function createMailtoUrl (articleTitle, shareUrl) { function copyToClipboard (event) { - const urlSection = event.target.closest('fieldset > .js-gift-article__url-section'); + const urlSection = event.target.closest('.js-gift-article__url-section'); const inputEl = urlSection.querySelector('input'); const oldContentEditable = inputEl.contentEditable; const oldReadOnly = inputEl.readOnly; From f6e0febb0f1f7ee44576259cafd6dcd7363a92d3 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Fri, 5 Oct 2018 17:11:26 +0100 Subject: [PATCH 144/760] switch to up-to-date jsx-a11y rule for labels --- .eslintrc.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index e3967a3f0..c34913c6b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -35,7 +35,10 @@ module.exports = { // We don't use display names for SFCs 'react/display-name': 'off', // This rule is intended to catch < or > but it's too eager - 'react/no-unescaped-entities': 'off' + 'react/no-unescaped-entities': 'off', + // this rule is deprecated and replaced with label-has-associated-control + 'jsx-a11y/label-has-for': 'off', + 'jsx-a11y/label-has-associated-control': 'error', }, overrides: [ { From 5efea66353ac4d530d2849ead643f08f493d7739 Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Thu, 18 Oct 2018 15:21:48 +0100 Subject: [PATCH 145/760] Append shareType querystring for tracking non-gifts --- components/x-gift-article/src/lib/props-composer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 83925339b..b2ad0e1bc 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -30,7 +30,7 @@ export class GiftArticlePropsComposer { this.urls = { dummy: 'https://on.ft.com/gift_link', gift: undefined, - nonGift: this.articleUrl + nonGift: `${this.articleUrl}?shareType=nongift` }; this.urlTypes = { From 48f7012224ab69bd2ec08fb4ce8f34be7a30f54d Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Thu, 18 Oct 2018 15:26:19 +0100 Subject: [PATCH 146/760] fixup! Append shareType querystring for tracking non-gifts --- __tests__/__snapshots__/snapshots.test.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 3dc384fb8..99be288ff 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -26,7 +26,7 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a name="non-gift-link" readOnly={true} type="text" - value="https://www.ft.com/content/blahblahblah" + value="https://www.ft.com/content/blahblahblah?shareType=nongift" /> <div className="GiftArticle_message__2zqH2" From 3226b023a03f6f67c2439693b65cf642f787063d Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Tue, 30 Oct 2018 10:31:13 +0000 Subject: [PATCH 147/760] Tracking querystring is added to non-gift urls --- __tests__/__snapshots__/snapshots.test.js.snap | 2 +- components/x-gift-article/src/GiftArticle.jsx | 13 ++++++------- components/x-gift-article/src/lib/props-composer.js | 2 +- components/x-gift-article/stories/free-article.js | 3 ++- components/x-gift-article/stories/native-share.js | 3 ++- .../x-gift-article/stories/with-gift-credits.js | 3 ++- .../x-gift-article/stories/without-gift-credits.js | 3 ++- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 99be288ff..9e367299c 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -44,7 +44,7 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a > <a className="o-buttons o-buttons--primary o-buttons--big" - href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" + href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah%3FshareType%3Dnongift" onClick={[Function]} rel="noopener noreferrer" target="_blank" diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index b23e9a755..169157520 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -8,21 +8,20 @@ import api from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; import tracking from './lib/tracking'; -const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId, composer }) => ({ +const withGiftFormActions = withActions(({ articleId, sessionId, composer }) => ({ showGiftUrlSection() { return composer.showGiftUrlSection(); }, async showNonGiftUrlSection() { - if (composer.isNonGiftUrlShortened) { - return composer.showNonGiftUrlSection(); - } else { - const { url, isShortened } = await api.getShorterUrl(articleUrl); + if (!composer.isNonGiftUrlShortened) { + const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); if (isShortened) { composer.setShortenedNonGiftUrl(url); } - return composer.showNonGiftUrlSection(); } + + return composer.showNonGiftUrlSection(); }, async createGiftUrl() { @@ -49,7 +48,7 @@ const withGiftFormActions = withActions(({ articleId, articleUrl, sessionId, com }, async getShorterNonGiftUrl() { - const { url, isShortened } = await api.getShorterUrl(articleUrl); + const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); if (isShortened) { return composer.setShortenedNonGiftUrl(url); diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index b2ad0e1bc..38dec5759 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -41,7 +41,7 @@ export class GiftArticlePropsComposer { this.mailtoUrls = { gift: undefined, - nonGift: createMailtoUrl(this.articleTitle, this.articleUrl) + nonGift: createMailtoUrl(this.articleTitle, `${this.articleUrl}?shareType=nongift`) }; this.mobileShareLinks = this.showMobileShareLinks ? { diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index ff107990d..11c58a67a 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -1,4 +1,5 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah'; +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; exports.title = 'Free article'; @@ -28,7 +29,7 @@ exports.fetchMock = fetchMock => { } ) .get( - `/article/shorten-url/${ encodeURIComponent(articleUrl) }`, + `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, { shortenedUrl: 'https://shortened-non-gift-url' } ); }; diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/stories/native-share.js index d847058f7..0cb11193c 100644 --- a/components/x-gift-article/stories/native-share.js +++ b/components/x-gift-article/stories/native-share.js @@ -1,5 +1,6 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah'; const articleUrlRedeemed = 'https://gift-url-redeemed'; +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; exports.title = 'With native share on App'; @@ -36,7 +37,7 @@ exports.fetchMock = fetchMock => { { shortenedUrl: 'https://shortened-gift-url' } ) .get( - `/article/shorten-url/${ encodeURIComponent(articleUrl) }`, + `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, { shortenedUrl: 'https://shortened-non-gift-url' } ) .post( diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 2d8f1d1ca..d52b86500 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -1,5 +1,6 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah'; const articleUrlRedeemed = 'https://gift-url-redeemed'; +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; exports.title = 'With gift credits'; @@ -36,7 +37,7 @@ exports.fetchMock = fetchMock => { { shortenedUrl: 'https://shortened-gift-url' } ) .get( - `/article/shorten-url/${ encodeURIComponent(articleUrl) }`, + `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, { shortenedUrl: 'https://shortened-non-gift-url' } ) .post( diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index bb32aeea8..0a94e0a50 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -1,4 +1,5 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah'; +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; exports.title = 'Without gift credits'; @@ -28,7 +29,7 @@ exports.fetchMock = fetchMock => { } ) .get( - `/article/shorten-url/${ encodeURIComponent(articleUrl) }`, + `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, { shortenedUrl: 'https://shortened-non-gift-url' } ); }; From d8b186a84d240a9931769bf1221e34f135b0b273 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Tue, 9 Oct 2018 10:09:25 +0100 Subject: [PATCH 148/760] move logic into activate action (currently actions can't call other actions) --- components/x-gift-article/src/GiftArticle.jsx | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 169157520..c75feb74b 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -8,7 +8,7 @@ import api from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; import tracking from './lib/tracking'; -const withGiftFormActions = withActions(({ articleId, sessionId, composer }) => ({ +const withGiftFormActions = withActions(({ articleId, sessionId, isFreeArticle, composer }) => ({ showGiftUrlSection() { return composer.showGiftUrlSection(); }, @@ -36,25 +36,6 @@ const withGiftFormActions = withActions(({ articleId, sessionId, composer }) => } }, - async getAllowance() { - const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); - - // avoid to use giftCredits >= 0 because it returns true when null and "" - if (giftCredits > 0 || giftCredits === 0) { - return composer.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); - } else { - // TODO do something - } - }, - - async getShorterNonGiftUrl() { - const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); - - if (isShortened) { - return composer.setShortenedNonGiftUrl(url); - } - }, - copyGiftUrl(event) { const giftUrl = composer.urls.gift; copyToClipboard(event); @@ -84,16 +65,30 @@ const withGiftFormActions = withActions(({ articleId, sessionId, composer }) => }, shareByNativeShare() { - // TODO display native share + throw new Error(`shareByNativeShare should be implemented by x-gift-article's consumers`); + }, + + async activate() { + if(isFreeArticle) { + const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); + + if (isShortened) { + return composer.setShortenedNonGiftUrl(url); + } + } else { + const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); + + // avoid to use giftCredits >= 0 because it returns true when null and "" + if (giftCredits > 0 || giftCredits === 0) { + return composer.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); + } else { + // TODO do something + } + } } })); const BaseTemplate = (props) => { - document.body.addEventListener('xDash.giftArticle.activate', () => { - props.isFreeArticle ? - props.actions.getShorterNonGiftUrl() : props.actions.getAllowance(); - }) - return props.isLoading ? <Loading/> : <Form {...props}/>; }; From 518c8c4b107cc50ea43b84a33e811aa3e707472c Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Wed, 10 Oct 2018 11:20:39 +0100 Subject: [PATCH 149/760] Allow api protocol and domain to be set via props. --- components/x-gift-article/src/GiftArticle.jsx | 17 ++++--- components/x-gift-article/src/lib/api.js | 44 ++++++++++++------- .../x-gift-article/src/lib/props-composer.js | 5 +++ 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index c75feb74b..f39029ac3 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -4,11 +4,17 @@ import { withActions } from '@financial-times/x-interaction'; import Loading from './Loading'; import Form from './Form'; -import api from './lib/api'; +import ApiClient from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; import tracking from './lib/tracking'; -const withGiftFormActions = withActions(({ articleId, sessionId, isFreeArticle, composer }) => ({ +const withGiftFormActions = withActions(({ articleId, sessionId, isFreeArticle, composer }) => { + const api = new ApiClient({ + protocol: composer.api.protocol, + domain: composer.api.domain + }); + + return { showGiftUrlSection() { return composer.showGiftUrlSection(); }, @@ -21,7 +27,7 @@ const withGiftFormActions = withActions(({ articleId, sessionId, isFreeArticle, } } - return composer.showNonGiftUrlSection(); + return composer.showNonGiftUrlSection(); }, async createGiftUrl() { @@ -69,7 +75,7 @@ const withGiftFormActions = withActions(({ articleId, sessionId, isFreeArticle, }, async activate() { - if(isFreeArticle) { + if (isFreeArticle) { const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); if (isShortened) { @@ -86,7 +92,8 @@ const withGiftFormActions = withActions(({ articleId, sessionId, isFreeArticle, } } } -})); + } +}); const BaseTemplate = (props) => { return props.isLoading ? <Loading/> : <Form {...props}/>; diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index fbaf2fdf3..7c311908d 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -1,9 +1,23 @@ -const REDEMPTION_LIMIT = 3; +export default class ApiClient { + constructor ({ protocol, domain } = {}) { + this.protocol = protocol; + this.domain = domain; + this.redemptionLimit = 3; + } + + getFetchUrl(url) { + if (this.protocol && this.domain) { + return `${this.protocol}://${this.domain}/${url}`; + } + + return url; + } + + async getGiftArticleAllowance() { + const url = this.getFetchUrl('/article-email/credits'); -module.exports = { - getGiftArticleAllowance: async () => { try { - const response = await fetch('/article-email/credits', { credentials: 'same-origin' }); + const response = await fetch(url, { credentials: 'same-origin' }); const json = await response.json(); return { @@ -11,16 +25,16 @@ module.exports = { giftCredits: json.credits.remainingCredits, nextRenewalDate: json.credits.renewalDate }; - } catch (e) { return { monthlyAllowance: undefined, giftCredits: undefined, nextRenewalDate: undefined }; } + } - }, + async getGiftUrl(articleId, sessionId) { + const url = this.getFetchUrl('/article-email/gift-link'); - getGiftUrl: async (articleId, sessionId) => { try { - const response = await fetch('/article-email/gift-link', { + const response = await fetch(url, { credentials: 'same-origin', method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -37,20 +51,19 @@ module.exports = { return { ...body, - redemptionLimit: REDEMPTION_LIMIT + redemptionLimit: this.redemptionLimit }; - } catch (e) { return { redemptionUrl: undefined, redemptionLimit: undefined }; } - }, + } - getShorterUrl: async (originalUrl) => { + async getShorterUrl(originalUrl) { + const fetchUrl = this.getFetchUrl('/article/shorten-url/' + encodeURIComponent(originalUrl)); let url = originalUrl; let isShortened = false; try { - const fetchUrl = '/article/shorten-url/' + encodeURIComponent(originalUrl); const response = await fetch(fetchUrl, { credentials: 'same-origin' }); const json = await response.json(); @@ -59,9 +72,10 @@ module.exports = { url = json.shortenedUrl; } } catch (e) { - // do nothing becasuse it just returns original url at the end + // do nothing because it just returns original url at the end } return { url, isShortened }; - }, + } } + diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 38dec5759..00c6a1bc2 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -50,6 +50,11 @@ export class GiftArticlePropsComposer { linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(this.articleUrl)}&title=${encodeURIComponent(this.articleTitle)}&source=Financial+Times`, whatsapp: `whatsapp://send?text=${encodeURIComponent(this.articleTitle)}%20-%20${encodeURIComponent(this.articleUrl)}` } : undefined; + + this.api = { + protocol: props.apiProtocol, + domain: props.apiDomain + } } getDefault() { From 03d8f935ed8534cff00c30441495cac4d6546a3a Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Wed, 10 Oct 2018 11:39:20 +0100 Subject: [PATCH 150/760] Include credentials on api fetches. --- components/x-gift-article/src/lib/api.js | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 7c311908d..a9afa62f5 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -5,20 +5,26 @@ export default class ApiClient { this.redemptionLimit = 3; } - getFetchUrl(url) { + getFetchUrl(path) { if (this.protocol && this.domain) { - return `${this.protocol}://${this.domain}/${url}`; + return `${this.protocol}://${this.domain}/${path}`; } - return url; + return path; } - async getGiftArticleAllowance() { - const url = this.getFetchUrl('/article-email/credits'); + fetchJson(path, additionalOptions) { + const url = this.getFetchUrl(path); + const options = Object.assign({ + credentials: 'include' + }, additionalOptions); + + return fetch(url, options).then(response => response.json()); + } + async getGiftArticleAllowance() { try { - const response = await fetch(url, { credentials: 'same-origin' }); - const json = await response.json(); + const json = await this.fetchJson('/article-email/credits'); return { monthlyAllowance: json.credits.allowance, @@ -31,11 +37,8 @@ export default class ApiClient { } async getGiftUrl(articleId, sessionId) { - const url = this.getFetchUrl('/article-email/gift-link'); - try { - const response = await fetch(url, { - credentials: 'same-origin', + const json = await this.fetchJson('/article-email/gift-link', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -44,13 +47,12 @@ export default class ApiClient { }) }); - const body = response.ok ? await response.json() : {}; - if (body.errors) { - throw new Error(`Failed to get gift article link: ${body.errors.join(', ')}`); + if (json.errors) { + throw new Error(`Failed to get gift article link: ${json.errors.join(', ')}`); } return { - ...body, + ...json, redemptionLimit: this.redemptionLimit }; } catch (e) { @@ -59,13 +61,11 @@ export default class ApiClient { } async getShorterUrl(originalUrl) { - const fetchUrl = this.getFetchUrl('/article/shorten-url/' + encodeURIComponent(originalUrl)); let url = originalUrl; let isShortened = false; try { - const response = await fetch(fetchUrl, { credentials: 'same-origin' }); - const json = await response.json(); + const json = await this.fetchJson('/article/shorten-url/' + encodeURIComponent(originalUrl)); if (json.shortenedUrl) { isShortened = true; From 4ca21082cbbb7d5d353c9bdf0269e4437c8525b7 Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Wed, 10 Oct 2018 13:36:15 +0100 Subject: [PATCH 151/760] Support protocol-relative api fetches. --- components/x-gift-article/src/lib/api.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index a9afa62f5..596326d67 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -6,11 +6,16 @@ export default class ApiClient { } getFetchUrl(path) { - if (this.protocol && this.domain) { - return `${this.protocol}://${this.domain}/${path}`; + let base = ''; + if (this.domain) { + base = `//${this.domain}`; + + if (this.protocol) { + base = `${this.protocol}:${base}`; + } } - return path; + return `${base}${path}`; } fetchJson(path, additionalOptions) { From b9cb4eb495e2d7b39b14a4ea457d91c86c4675c4 Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Wed, 10 Oct 2018 13:53:09 +0100 Subject: [PATCH 152/760] Document apiProtocol and apiDomain props. --- components/x-gift-article/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index a4cb839be..6d8e49853 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -55,3 +55,5 @@ Property | Type | Required | Note `sessionId` | String | yes | This is needed to get a gift url. `showMobileShareLinks` | Boolean | no | `nativeShare` | Boolean | no | This is a property for App to display Native Sharing. +`apiProtocol` | String | no | The protocol to use when making requests to the gift article and URL shortening services. Ignored if `apiDomain` is not set. +`apiDomain` | String | no | The domain to use when making requests to the gift article and URL shortening services. From 78dd6fa5972c4cb8bb7f12b6204d2d6c2d655a22 Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Tue, 16 Oct 2018 15:49:48 +0100 Subject: [PATCH 153/760] Use new next-article endpoints for gift and url shortening services. --- components/x-gift-article/src/GiftArticle.jsx | 110 +++++++++--------- components/x-gift-article/src/lib/api.js | 23 ++-- .../x-gift-article/src/lib/props-composer.js | 1 - 3 files changed, 62 insertions(+), 72 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index f39029ac3..ee24ca0cd 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -15,84 +15,84 @@ const withGiftFormActions = withActions(({ articleId, sessionId, isFreeArticle, }); return { - showGiftUrlSection() { - return composer.showGiftUrlSection(); - }, + showGiftUrlSection() { + return composer.showGiftUrlSection(); + }, - async showNonGiftUrlSection() { + async showNonGiftUrlSection() { if (!composer.isNonGiftUrlShortened) { const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); - if (isShortened) { - composer.setShortenedNonGiftUrl(url); - } + if (isShortened) { + composer.setShortenedNonGiftUrl(url); + } } - return composer.showNonGiftUrlSection(); - }, + return composer.showNonGiftUrlSection(); + }, - async createGiftUrl() { - const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(articleId, sessionId); + async createGiftUrl() { + const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(articleId); - if (redemptionUrl) { - const { url, isShortened } = await api.getShorterUrl(redemptionUrl); - tracking.createGiftLink(url, redemptionUrl); - return composer.setGiftUrl(url, redemptionLimit, isShortened); - } else { - // TODO do something - } - }, + if (redemptionUrl) { + const { url, isShortened } = await api.getShorterUrl(redemptionUrl); + tracking.createGiftLink(url, redemptionUrl); + return composer.setGiftUrl(url, redemptionLimit, isShortened); + } else { + // TODO do something + } + }, - copyGiftUrl(event) { - const giftUrl = composer.urls.gift; - copyToClipboard(event); - tracking.copyLink('giftLink', giftUrl); + copyGiftUrl(event) { + const giftUrl = composer.urls.gift; + copyToClipboard(event); + tracking.copyLink('giftLink', giftUrl); - return composer.showCopyConfirmation(); - }, + return composer.showCopyConfirmation(); + }, - copyNonGiftUrl(event) { - const nonGiftUrl = composer.urls.nonGift; - copyToClipboard(event); - tracking.copyLink('nonGiftLink', nonGiftUrl); + copyNonGiftUrl(event) { + const nonGiftUrl = composer.urls.nonGift; + copyToClipboard(event); + tracking.copyLink('nonGiftLink', nonGiftUrl); - return composer.showCopyConfirmation(); - }, + return composer.showCopyConfirmation(); + }, - emailGiftUrl() { - tracking.emailLink('giftLink', composer.urls.gift); - }, + emailGiftUrl() { + tracking.emailLink('giftLink', composer.urls.gift); + }, - emailNonGiftUrl() { - tracking.emailLink('nonGiftLink', composer.urls.nonGift); - }, + emailNonGiftUrl() { + tracking.emailLink('nonGiftLink', composer.urls.nonGift); + }, - hideCopyConfirmation() { - return composer.hideCopyConfirmation(); - }, + hideCopyConfirmation() { + return composer.hideCopyConfirmation(); + }, - shareByNativeShare() { - throw new Error(`shareByNativeShare should be implemented by x-gift-article's consumers`); - }, + shareByNativeShare() { + throw new Error(`shareByNativeShare should be implemented by x-gift-article's consumers`); + }, - async activate() { + async activate() { if (isFreeArticle) { const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); - if (isShortened) { - return composer.setShortenedNonGiftUrl(url); - } - } else { - const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); - - // avoid to use giftCredits >= 0 because it returns true when null and "" - if (giftCredits > 0 || giftCredits === 0) { - return composer.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); + if (isShortened) { + return composer.setShortenedNonGiftUrl(url); + } } else { - // TODO do something + const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); + + // avoid to use giftCredits >= 0 because it returns true when null and "" + if (giftCredits > 0 || giftCredits === 0) { + return composer.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); + } else { + // TODO do something + } } } } - } }); const BaseTemplate = (props) => { diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 596326d67..e10425d73 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -2,7 +2,6 @@ export default class ApiClient { constructor ({ protocol, domain } = {}) { this.protocol = protocol; this.domain = domain; - this.redemptionLimit = 3; } getFetchUrl(path) { @@ -29,36 +28,28 @@ export default class ApiClient { async getGiftArticleAllowance() { try { - const json = await this.fetchJson('/article-email/credits'); + const json = await this.fetchJson('/article/gift-credits'); return { - monthlyAllowance: json.credits.allowance, - giftCredits: json.credits.remainingCredits, - nextRenewalDate: json.credits.renewalDate + monthlyAllowance: json.allowance, + giftCredits: json.remainingCredits, + nextRenewalDate: json.renewalDate }; } catch (e) { return { monthlyAllowance: undefined, giftCredits: undefined, nextRenewalDate: undefined }; } } - async getGiftUrl(articleId, sessionId) { + async getGiftUrl(articleId) { try { - const json = await this.fetchJson('/article-email/gift-link', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - contentUUID: articleId, - ftSessionSecure: sessionId - }) - }); + const json = await this.fetchJson('/article/gift-link/' + encodeURIComponent(articleId)); if (json.errors) { throw new Error(`Failed to get gift article link: ${json.errors.join(', ')}`); } return { - ...json, - redemptionLimit: this.redemptionLimit + ...json }; } catch (e) { return { redemptionUrl: undefined, redemptionLimit: undefined }; diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 00c6a1bc2..e86e253f7 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -15,7 +15,6 @@ export class GiftArticlePropsComposer { this.articleId = props.articleId; this.title = props.title || 'Share this article'; - this.sessionId = props.sessionId; this.giftCredits = undefined; this.monthlyAllowance = undefined; From db75f23121c469b1e00566778a97ab5f5b242315 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Wed, 31 Oct 2018 13:51:00 +0000 Subject: [PATCH 154/760] fix gift-article fetch mocks --- components/x-gift-article/stories/native-share.js | 7 ++++--- components/x-gift-article/stories/with-gift-credits.js | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/stories/native-share.js index 0cb11193c..377fe05f9 100644 --- a/components/x-gift-article/stories/native-share.js +++ b/components/x-gift-article/stories/native-share.js @@ -1,3 +1,4 @@ +const articleId = 'article id'; const articleUrl = 'https://www.ft.com/content/blahblahblah'; const articleUrlRedeemed = 'https://gift-url-redeemed'; const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; @@ -9,7 +10,7 @@ exports.data = { isFreeArticle: false, articleUrl, articleTitle: 'Title Title Title Title', - articleId: 'article id', + articleId, sessionId: 'session id', nativeShare: true, id: 'base-gift-article-static-id' @@ -40,8 +41,8 @@ exports.fetchMock = fetchMock => { `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, { shortenedUrl: 'https://shortened-non-gift-url' } ) - .post( - '/article-email/gift-link', + .get( + `/article/gift-link/${ encodeURIComponent(articleId) }`, { 'redemptionUrl': articleUrlRedeemed, 'remainingAllowance': 1 diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index d52b86500..1b592664d 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -1,3 +1,4 @@ +const articleId = 'article id'; const articleUrl = 'https://www.ft.com/content/blahblahblah'; const articleUrlRedeemed = 'https://gift-url-redeemed'; const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; @@ -9,7 +10,7 @@ exports.data = { isFreeArticle: false, articleUrl, articleTitle: 'Title Title Title Title', - articleId: 'article id', + articleId, sessionId: 'session id', showMobileShareLinks: true, id: 'base-gift-article-static-id' @@ -40,8 +41,8 @@ exports.fetchMock = fetchMock => { `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, { shortenedUrl: 'https://shortened-non-gift-url' } ) - .post( - '/article-email/gift-link', + .get( + `/article/gift-link/${ encodeURIComponent(articleId) }`, { 'redemptionUrl': articleUrlRedeemed, 'remainingAllowance': 1 From d5adf1cfaa979f8434f001282cb31f14f534447b Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Wed, 31 Oct 2018 14:44:39 +0000 Subject: [PATCH 155/760] lint --- components/x-gift-article/src/GiftArticle.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index ee24ca0cd..775a62732 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -8,7 +8,7 @@ import ApiClient from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; import tracking from './lib/tracking'; -const withGiftFormActions = withActions(({ articleId, sessionId, isFreeArticle, composer }) => { +const withGiftFormActions = withActions(({ articleId, isFreeArticle, composer }) => { const api = new ApiClient({ protocol: composer.api.protocol, domain: composer.api.domain From 5a44c11652591daaf6c0568feef5c4f3f230d43f Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Thu, 1 Nov 2018 10:09:26 +0000 Subject: [PATCH 156/760] forward actionsRef to GiftArticle --- components/x-gift-article/src/lib/props-composer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index e86e253f7..69f0d1bd9 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -26,6 +26,8 @@ export class GiftArticlePropsComposer { this.isGiftUrlShortened = false; this.isNonGiftUrlShortened = false; + this.actionsRef = props.actionsRef; + this.urls = { dummy: 'https://on.ft.com/gift_link', gift: undefined, @@ -68,7 +70,8 @@ export class GiftArticlePropsComposer { showCopyButton: this.showCopyButton, showShareButtons: this.showMobileShareLinks, showNativeShareButton: this.showNativeShareButton, - mobileShareLinks: this.mobileShareLinks + mobileShareLinks: this.mobileShareLinks, + actionsRef: this.actionsRef, }; const additionalProps = this.isFreeArticle ? this.showNonGiftUrlSection() : this.showGiftUrlSection(); From ce17e199d7794bf896c7e62a62c560409906705f Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Thu, 1 Nov 2018 11:43:12 +0000 Subject: [PATCH 157/760] forward actions as well --- components/x-gift-article/src/lib/props-composer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js index 69f0d1bd9..f253744d3 100644 --- a/components/x-gift-article/src/lib/props-composer.js +++ b/components/x-gift-article/src/lib/props-composer.js @@ -26,6 +26,7 @@ export class GiftArticlePropsComposer { this.isGiftUrlShortened = false; this.isNonGiftUrlShortened = false; + this.actions = props.actions; this.actionsRef = props.actionsRef; this.urls = { From 27e7a138e7141770a0eb3c31716882a3b7343c8d Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Mon, 5 Nov 2018 10:09:33 +0000 Subject: [PATCH 158/760] formatting changes --- components/x-gift-article/src/Buttons.jsx | 26 +++++- components/x-gift-article/src/Form.jsx | 25 +----- components/x-gift-article/src/GiftArticle.jsx | 16 ++-- components/x-gift-article/src/Message.jsx | 3 +- .../src/RadioButtonsSection.jsx | 3 +- components/x-gift-article/src/Url.jsx | 3 +- components/x-gift-article/src/UrlSection.jsx | 85 +++++++++++-------- 7 files changed, 88 insertions(+), 73 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index aea7f0ca4..ad9586b4e 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -17,9 +17,19 @@ const ButtonWithGapClassNames = [ ].join(' '); -export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, - copyGiftUrl, copyNonGiftUrl, emailGiftUrl, emailNonGiftUrl, - showCopyButton, showNativeShareButton, shareByNativeShare }) => { +export default ({ + shareType, + isGiftUrlCreated, + mailtoUrl, + createGiftUrl, + copyGiftUrl, + copyNonGiftUrl, + emailGiftUrl, + emailNonGiftUrl, + showCopyButton, + showNativeShareButton, + shareByNativeShare +}) => { if (isGiftUrlCreated || shareType === SHARE_TYPE_NON_GIFT) { @@ -33,7 +43,15 @@ export default ({ shareType, isGiftUrlCreated, mailtoUrl, createGiftUrl, return ( <div className={ ButtonsClassName }> - { showCopyButton && <button className={ ButtonWithGapClassNames } type="button" onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl }>Copy link</button> } + { showCopyButton && + <button + className={ ButtonWithGapClassNames } + type="button" + onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl } + > + Copy link + </button> + } <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === SHARE_TYPE_GIFT ? emailGiftUrl : emailNonGiftUrl }>Email link</a> </div> ); diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 5c1ab683b..be34ffb45 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -15,7 +15,6 @@ export default (props) => ( <div className={ styles.container }> <form name="gift-form"> <fieldset className={ formClassNames }> - <Title title={ props.title }/> { !props.isFreeArticle && <RadioButtonsSection @@ -24,34 +23,14 @@ export default (props) => ( showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> } - <UrlSection - shareType={ props.shareType } - isGiftUrlCreated={ props.isGiftUrlCreated } - isFreeArticle={ props.isFreeArticle } - url={ props.url } - urlType={ props.urlType } - giftCredits={ props.giftCredits } - monthlyAllowance={ props.monthlyAllowance } - nextRenewalDateText={ props.nextRenewalDateText } - mailtoUrl={ props.mailtoUrl } - createGiftUrl={ props.actions.createGiftUrl } - copyGiftUrl={ props.actions.copyGiftUrl } - copyNonGiftUrl={ props.actions.copyNonGiftUrl } - emailGiftUrl={ props.actions.emailGiftUrl } - emailNonGiftUrl={ props.actions.emailNonGiftUrl } - redemptionLimit={ props.redemptionLimit } - showCopyButton={ props.showCopyButton } - showNativeShareButton={ props.showNativeShareButton } - shareByNativeShare={ props.actions.shareByNativeShare }/> - + <UrlSection {...props} /> </fieldset> </form> - + { props.showCopyConfirmation && <CopyConfirmation hideCopyConfirmation={ props.actions.hideCopyConfirmation }/> } { props.showShareButtons && <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> } - </div> ); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 775a62732..04d4dfda9 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -20,14 +20,14 @@ const withGiftFormActions = withActions(({ articleId, isFreeArticle, composer }) }, async showNonGiftUrlSection() { - if (!composer.isNonGiftUrlShortened) { - const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); - if (isShortened) { - composer.setShortenedNonGiftUrl(url); - } - } + if (!composer.isNonGiftUrlShortened) { + const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); + if (isShortened) { + composer.setShortenedNonGiftUrl(url); + } + } - return composer.showNonGiftUrlSection(); + return composer.showNonGiftUrlSection(); }, async createGiftUrl() { @@ -76,7 +76,7 @@ const withGiftFormActions = withActions(({ articleId, isFreeArticle, composer }) async activate() { if (isFreeArticle) { - const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); + const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); if (isShortened) { return composer.setShortenedNonGiftUrl(url); diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 04a3c1eb3..6b89c0161 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -36,7 +36,8 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month return ( <div className={ messageClassName }> You have <span className={ boldTextClassName }>{ giftCredits } gift article { giftCredits === 1 ? 'credit' : 'credits' }</span> left this month - </div>); + </div> + ); } if (shareType === SHARE_TYPE_NON_GIFT) { diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index f103b0d05..a4e40e04d 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -19,7 +19,8 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( className="o-forms__radio" id="giftLink" checked={ shareType === SHARE_TYPE_GIFT } - onChange={ showGiftUrlSection }/> + onChange={ showGiftUrlSection } + /> <label htmlFor="giftLink" className="o-forms__label"> with <span className={ boldTextClassName }>anyone</span> (uses 1 gift credit) diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 83fc0162b..f325f8ce5 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -15,6 +15,7 @@ export default ({ shareType, isGiftUrlCreated, url, urlType }) => { value={ url } className={ urlClassNames } disabled={ shareType === SHARE_TYPE_GIFT && !isGiftUrlCreated } - readOnly/> + readOnly + /> ); }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index ae1b03533..1ef84ce92 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -10,11 +10,26 @@ const urlSectionClassNames = [ styles['url-section'] ].join(' '); -export default ({ shareType, isGiftUrlCreated, isFreeArticle, - url, urlType, giftCredits, monthlyAllowance, nextRenewalDateText, - mailtoUrl, createGiftUrl, copyGiftUrl, copyNonGiftUrl, - emailGiftUrl, emailNonGiftUrl, redemptionLimit, showCopyButton, - showNativeShareButton, shareByNativeShare }) => { +export default ({ + shareType, + isGiftUrlCreated, + isFreeArticle, + url, + urlType, + giftCredits, + monthlyAllowance, + nextRenewalDateText, + mailtoUrl, + createGiftUrl, + copyGiftUrl, + copyNonGiftUrl, + emailGiftUrl, + emailNonGiftUrl, + redemptionLimit, + showCopyButton, + showNativeShareButton, + shareByNativeShare +}) => { const hideUrlShareElements = ( giftCredits === 0 && shareType === SHARE_TYPE_GIFT ); const showUrlShareElements = !hideUrlShareElements; @@ -25,36 +40,36 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, data-section-id={ shareType + 'Link' } data-trackable={ shareType + 'Link' }> - { showUrlShareElements && <Url - shareType={ shareType } - isGiftUrlCreated={ isGiftUrlCreated } - url={ url } - urlType={ urlType }/> - } - - <Message - shareType={ shareType } - isGiftUrlCreated={ isGiftUrlCreated } - isFreeArticle={ isFreeArticle } - giftCredits={ giftCredits } - monthlyAllowance={ monthlyAllowance } - nextRenewalDateText={ nextRenewalDateText } - redemptionLimit={ redemptionLimit } - /> - - { showUrlShareElements && <Buttons - shareType={ shareType } - isGiftUrlCreated={ isGiftUrlCreated } - mailtoUrl={ mailtoUrl } - createGiftUrl={ createGiftUrl } - copyGiftUrl={ copyGiftUrl } - copyNonGiftUrl={ copyNonGiftUrl } - emailGiftUrl={ emailGiftUrl } - emailNonGiftUrl={ emailNonGiftUrl } - showCopyButton={ showCopyButton } - showNativeShareButton={ showNativeShareButton } - shareByNativeShare={ shareByNativeShare }/> - } + { showUrlShareElements && <Url {...{ + shareType, + isGiftUrlCreated, + url, + urlType, + }} /> } + + <Message {...{ + shareType, + isGiftUrlCreated, + isFreeArticle, + giftCredits, + monthlyAllowance, + nextRenewalDateText, + redemptionLimit, + }} /> + + { showUrlShareElements && <Buttons {...{ + shareType, + isGiftUrlCreated, + mailtoUrl, + createGiftUrl, + copyGiftUrl, + copyNonGiftUrl, + emailGiftUrl, + emailNonGiftUrl, + showCopyButton, + showNativeShareButton, + shareByNativeShare, + }} />} </div> ); From 4c6954442a51bf291ecfca4d08761327d55d84c8 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Mon, 5 Nov 2018 13:19:03 +0000 Subject: [PATCH 159/760] wip gift article refactor --- components/x-gift-article/package.json | 2 +- components/x-gift-article/rollup.js | 2 +- components/x-gift-article/src/GiftArticle.jsx | 14 ++++++++------ .../x-gift-article/src/GiftArticleWrapper.jsx | 13 ------------- 4 files changed, 10 insertions(+), 21 deletions(-) delete mode 100644 components/x-gift-article/src/GiftArticleWrapper.jsx diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 8eb01bfba..35d8ed2ed 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -18,7 +18,7 @@ "license": "ISC", "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-interaction": "file:../x-interaction", + "@financial-times/x-interaction": "0.0.3", "classnames": "^2.2.6" }, "devDependencies": { diff --git a/components/x-gift-article/rollup.js b/components/x-gift-article/rollup.js index 30a17f3cd..f6112c551 100644 --- a/components/x-gift-article/rollup.js +++ b/components/x-gift-article/rollup.js @@ -1,4 +1,4 @@ const xRollup = require('@financial-times/x-rollup'); const pkg = require('./package.json'); -xRollup({ input: './src/GiftArticleWrapper.jsx', pkg }); +xRollup({ input: './src/GiftArticle.jsx', pkg }); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 04d4dfda9..d89c13544 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -8,7 +8,9 @@ import ApiClient from './lib/api'; import { copyToClipboard } from './lib/share-link-actions'; import tracking from './lib/tracking'; -const withGiftFormActions = withActions(({ articleId, isFreeArticle, composer }) => { +const withGiftFormActions = withActions(props => { + const composer = new GiftArticlePropsComposer(props); + const api = new ApiClient({ protocol: composer.api.protocol, domain: composer.api.domain @@ -47,7 +49,7 @@ const withGiftFormActions = withActions(({ articleId, isFreeArticle, composer }) copyToClipboard(event); tracking.copyLink('giftLink', giftUrl); - return composer.showCopyConfirmation(); + return { showCopyConfirmation: true }; }, copyNonGiftUrl(event) { @@ -55,7 +57,7 @@ const withGiftFormActions = withActions(({ articleId, isFreeArticle, composer }) copyToClipboard(event); tracking.copyLink('nonGiftLink', nonGiftUrl); - return composer.showCopyConfirmation(); + return { showCopyConfirmation: true }; }, emailGiftUrl() { @@ -67,7 +69,7 @@ const withGiftFormActions = withActions(({ articleId, isFreeArticle, composer }) }, hideCopyConfirmation() { - return composer.hideCopyConfirmation(); + return { showCopyConfirmation: false }; }, shareByNativeShare() { @@ -95,10 +97,10 @@ const withGiftFormActions = withActions(({ articleId, isFreeArticle, composer }) } }); -const BaseTemplate = (props) => { +const BaseGiftArticle = (props) => { return props.isLoading ? <Loading/> : <Form {...props}/>; }; -const GiftArticle = withGiftFormActions(BaseTemplate); +const GiftArticle = withGiftFormActions(BaseGiftArticle); export { GiftArticle }; diff --git a/components/x-gift-article/src/GiftArticleWrapper.jsx b/components/x-gift-article/src/GiftArticleWrapper.jsx deleted file mode 100644 index fca5f7c70..000000000 --- a/components/x-gift-article/src/GiftArticleWrapper.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import { h } from '@financial-times/x-engine'; -import { GiftArticlePropsComposer } from './lib/props-composer'; -import { GiftArticle } from './GiftArticle'; - -const GiftArticleWrapper = (props) => { - const propsComposer = new GiftArticlePropsComposer(props); - const composedProps = propsComposer.getDefault(); - composedProps.composer = propsComposer; - - return <GiftArticle {...composedProps}/>; -}; - -export { GiftArticleWrapper }; From e35d1b9429e9189618da9bf8faab98cba81a2aa5 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Mon, 5 Nov 2018 16:42:25 +0000 Subject: [PATCH 160/760] wip updaters --- components/x-gift-article/package.json | 2 +- components/x-gift-article/src/Buttons.jsx | 8 +- components/x-gift-article/src/GiftArticle.jsx | 181 +++++++++++------- components/x-gift-article/src/Message.jsx | 6 +- .../src/RadioButtonsSection.jsx | 6 +- components/x-gift-article/src/Url.jsx | 4 +- components/x-gift-article/src/UrlSection.jsx | 4 +- .../x-gift-article/src/lib/constants.js | 12 +- .../x-gift-article/src/lib/props-composer.js | 151 --------------- components/x-gift-article/src/lib/updaters.js | 0 10 files changed, 135 insertions(+), 239 deletions(-) delete mode 100644 components/x-gift-article/src/lib/props-composer.js create mode 100644 components/x-gift-article/src/lib/updaters.js diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 35d8ed2ed..092a3a68c 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -18,7 +18,7 @@ "license": "ISC", "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-interaction": "0.0.3", + "@financial-times/x-interaction": "0.0.4", "classnames": "^2.2.6" }, "devDependencies": { diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index ad9586b4e..3cba54ba7 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './lib/constants'; +import { shareType } from './lib/constants'; import styles from './GiftArticle.css'; const ButtonsClassName = styles.buttons; @@ -31,7 +31,7 @@ export default ({ shareByNativeShare }) => { - if (isGiftUrlCreated || shareType === SHARE_TYPE_NON_GIFT) { + if (isGiftUrlCreated || shareType === shareType.nonGift) { if (showNativeShareButton) { return ( @@ -47,12 +47,12 @@ export default ({ <button className={ ButtonWithGapClassNames } type="button" - onClick={ shareType === SHARE_TYPE_GIFT ? copyGiftUrl : copyNonGiftUrl } + onClick={ shareType === shareType.gift ? copyGiftUrl : copyNonGiftUrl } > Copy link </button> } - <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === SHARE_TYPE_GIFT ? emailGiftUrl : emailNonGiftUrl }>Email link</a> + <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === shareType.gift ? emailGiftUrl : emailNonGiftUrl }>Email link</a> </div> ); } diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index d89c13544..582f31d63 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -5,97 +5,136 @@ import Loading from './Loading'; import Form from './Form'; import ApiClient from './lib/api'; -import { copyToClipboard } from './lib/share-link-actions'; +import { copyToClipboard, createMailtoUrl } from './lib/share-link-actions'; import tracking from './lib/tracking'; +import * as updaters from './lib/updaters'; + +const isCopySupported = typeof document !== 'undefined' + && document.queryCommandSupported + && document.queryCommandSupported('copy'); + +const withGiftFormActions = withActions( + props => { + const api = new ApiClient({ + protocol: props.apiProtocol, + domain: props.apiDomain + }); + + return { + showGiftUrlSection() { + return updaters.showGiftUrlSection(); + }, + + async showNonGiftUrlSection() { + if (!updaters.isNonGiftUrlShortened) { + const { url, isShortened } = await api.getShorterUrl(updaters.urls.nonGift); + if (isShortened) { + updaters.setShortenedNonGiftUrl(url); + } + } -const withGiftFormActions = withActions(props => { - const composer = new GiftArticlePropsComposer(props); + return updaters.showNonGiftUrlSection(); + }, - const api = new ApiClient({ - protocol: composer.api.protocol, - domain: composer.api.domain - }); + async createGiftUrl() { + const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(articleId); - return { - showGiftUrlSection() { - return composer.showGiftUrlSection(); - }, + if (redemptionUrl) { + const { url, isShortened } = await api.getShorterUrl(redemptionUrl); + tracking.createGiftLink(url, redemptionUrl); + return updaters.setGiftUrl(url, redemptionLimit, isShortened); + } else { + // TODO do something + } + }, - async showNonGiftUrlSection() { - if (!composer.isNonGiftUrlShortened) { - const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); - if (isShortened) { - composer.setShortenedNonGiftUrl(url); - } - } + copyGiftUrl(event) { + const giftUrl = updaters.urls.gift; + copyToClipboard(event); + tracking.copyLink('giftLink', giftUrl); - return composer.showNonGiftUrlSection(); - }, + return { showCopyConfirmation: true }; + }, - async createGiftUrl() { - const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(articleId); + copyNonGiftUrl(event) { + const nonGiftUrl = updaters.urls.nonGift; + copyToClipboard(event); + tracking.copyLink('nonGiftLink', nonGiftUrl); - if (redemptionUrl) { - const { url, isShortened } = await api.getShorterUrl(redemptionUrl); - tracking.createGiftLink(url, redemptionUrl); - return composer.setGiftUrl(url, redemptionLimit, isShortened); - } else { - // TODO do something - } - }, + return { showCopyConfirmation: true }; + }, - copyGiftUrl(event) { - const giftUrl = composer.urls.gift; - copyToClipboard(event); - tracking.copyLink('giftLink', giftUrl); + emailGiftUrl() { + tracking.emailLink('giftLink', updaters.urls.gift); + }, - return { showCopyConfirmation: true }; - }, + emailNonGiftUrl() { + tracking.emailLink('nonGiftLink', updaters.urls.nonGift); + }, - copyNonGiftUrl(event) { - const nonGiftUrl = composer.urls.nonGift; - copyToClipboard(event); - tracking.copyLink('nonGiftLink', nonGiftUrl); + hideCopyConfirmation() { + return { showCopyConfirmation: false }; + }, - return { showCopyConfirmation: true }; - }, + shareByNativeShare() { + throw new Error(`shareByNativeShare should be implemented by x-gift-article's consumers`); + }, - emailGiftUrl() { - tracking.emailLink('giftLink', composer.urls.gift); - }, + async activate() { + if (isFreeArticle) { + const { url, isShortened } = await api.getShorterUrl(updaters.urls.nonGift); - emailNonGiftUrl() { - tracking.emailLink('nonGiftLink', composer.urls.nonGift); - }, + if (isShortened) { + return updaters.setShortenedNonGiftUrl(url); + } + } else { + const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); - hideCopyConfirmation() { - return { showCopyConfirmation: false }; + // avoid to use giftCredits >= 0 because it returns true when null and "" + if (giftCredits > 0 || giftCredits === 0) { + return updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); + } else { + // TODO do something + } + } + } + } + }, + props => ({ + title: 'Share this article', + giftCredits: undefined, + monthlyAllowance: undefined, + showCopyButton: isCopySupported, + isGiftUrlCreated: false, + isGiftUrlShortened: false, + isNonGiftUrlShortened: false, + + urls: { + dummy: 'https://on.ft.com/gift_link', + gift: undefined, + nonGift: `${props.articleUrl}?shareType=nongift` }, - shareByNativeShare() { - throw new Error(`shareByNativeShare should be implemented by x-gift-article's consumers`); + mailtoUrls: { + gift: undefined, + nonGift: createMailtoUrl(props.articleTitle, `${props.articleUrl}?shareType=nongift`) }, - async activate() { - if (isFreeArticle) { - const { url, isShortened } = await api.getShorterUrl(composer.urls.nonGift); - - if (isShortened) { - return composer.setShortenedNonGiftUrl(url); - } - } else { - const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); - - // avoid to use giftCredits >= 0 because it returns true when null and "" - if (giftCredits > 0 || giftCredits === 0) { - return composer.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); - } else { - // TODO do something - } + mobileShareLinks: props.showMobileShareLinks + ? { + facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(props.articleUrl)}&t=${encodeURIComponent(props.articleTitle)}`, + twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(props.articleUrl)}&text=${encodeURIComponent(props.articleTitle)}&via=financialtimes`, + linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(props.articleUrl)}&title=${encodeURIComponent(props.articleTitle)}&source=Financial+Times`, + whatsapp: `whatsapp://send?text=${encodeURIComponent(props.articleTitle)}%20-%20${encodeURIComponent(props.articleUrl)}` } - } - } -}); + : undefined, + + ...(props.isFreeArticle + ? updaters.showNonGiftUrlSection(props) + : updaters.showGiftUrlSection(props) + ), + }) +); const BaseGiftArticle = (props) => { return props.isLoading ? <Loading/> : <Form {...props}/>; diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 6b89c0161..9c45c522d 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './lib/constants'; +import { shareType } from './lib/constants'; import styles from './GiftArticle.css'; const messageClassName = styles.message; @@ -15,7 +15,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month ); } - if (shareType === SHARE_TYPE_GIFT) { + if (shareType === shareType.gift) { if (giftCredits === 0) { return ( <div className={ messageClassName }> @@ -40,7 +40,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month ); } - if (shareType === SHARE_TYPE_NON_GIFT) { + if (shareType === shareType.nonGift) { return ( <div className={ messageClassName }> This link can only be read by existing subscribers diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index a4e40e04d..017443428 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './lib/constants'; +import { shareType } from './lib/constants'; import styles from './GiftArticle.css'; const boldTextClassName = styles.bold; @@ -18,7 +18,7 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( value="giftLink" className="o-forms__radio" id="giftLink" - checked={ shareType === SHARE_TYPE_GIFT } + checked={ shareType === shareType.gift } onChange={ showGiftUrlSection } /> @@ -32,7 +32,7 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" - checked={ shareType === SHARE_TYPE_NON_GIFT } + checked={ shareType === shareType.nonGift } onChange={ showNonGiftUrlSection }/> <label htmlFor="nonGiftLink" className="o-forms__label"> diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index f325f8ce5..d658229e4 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { SHARE_TYPE_GIFT } from './lib/constants'; +import { shareType.gift } from './lib/constants'; import styles from './GiftArticle.css'; const urlClassNames = [ @@ -14,7 +14,7 @@ export default ({ shareType, isGiftUrlCreated, url, urlType }) => { name={ urlType } value={ url } className={ urlClassNames } - disabled={ shareType === SHARE_TYPE_GIFT && !isGiftUrlCreated } + disabled={ shareType === shareType.gift && !isGiftUrlCreated } readOnly /> ); diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 1ef84ce92..959340055 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { SHARE_TYPE_GIFT } from './lib/constants'; +import { shareType.gift } from './lib/constants'; import Url from './Url'; import Message from './Message'; import Buttons from './Buttons'; @@ -31,7 +31,7 @@ export default ({ shareByNativeShare }) => { - const hideUrlShareElements = ( giftCredits === 0 && shareType === SHARE_TYPE_GIFT ); + const hideUrlShareElements = ( giftCredits === 0 && shareType === shareType.gift ); const showUrlShareElements = !hideUrlShareElements; return ( diff --git a/components/x-gift-article/src/lib/constants.js b/components/x-gift-article/src/lib/constants.js index 920b72481..19aea63ba 100644 --- a/components/x-gift-article/src/lib/constants.js +++ b/components/x-gift-article/src/lib/constants.js @@ -1,2 +1,10 @@ -export const SHARE_TYPE_GIFT = 'gift'; -export const SHARE_TYPE_NON_GIFT = 'nonGift'; +export const shareType = { + gift: 'gift', + nonGift: 'nonGift' +}; + +export const urlType = { + dummy: 'example-gift-link', + gift: 'gift-link', + nonGift: 'non-gift-link' +}; diff --git a/components/x-gift-article/src/lib/props-composer.js b/components/x-gift-article/src/lib/props-composer.js deleted file mode 100644 index f253744d3..000000000 --- a/components/x-gift-article/src/lib/props-composer.js +++ /dev/null @@ -1,151 +0,0 @@ -import { createMailtoUrl } from './share-link-actions'; -import { SHARE_TYPE_GIFT, SHARE_TYPE_NON_GIFT } from './constants'; - -const isCopySupported = typeof document !== 'undefined' - && document.queryCommandSupported - && document.queryCommandSupported('copy'); -const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - -export class GiftArticlePropsComposer { - constructor(props) { - this.id = props.id; - this.isFreeArticle = props.isFreeArticle; - this.articleTitle = props.articleTitle; - this.articleUrl = props.articleUrl; - this.articleId = props.articleId; - - this.title = props.title || 'Share this article'; - this.giftCredits = undefined; - this.monthlyAllowance = undefined; - - this.showCopyButton = isCopySupported; - this.showMobileShareLinks = props.showMobileShareLinks; - this.showNativeShareButton = props.nativeShare; - - this.isGiftUrlCreated = false; - this.isGiftUrlShortened = false; - this.isNonGiftUrlShortened = false; - - this.actions = props.actions; - this.actionsRef = props.actionsRef; - - this.urls = { - dummy: 'https://on.ft.com/gift_link', - gift: undefined, - nonGift: `${this.articleUrl}?shareType=nongift` - }; - - this.urlTypes = { - dummy: 'example-gift-link', - gift: 'gift-link', - nonGift: 'non-gift-link' - }; - - this.mailtoUrls = { - gift: undefined, - nonGift: createMailtoUrl(this.articleTitle, `${this.articleUrl}?shareType=nongift`) - }; - - this.mobileShareLinks = this.showMobileShareLinks ? { - facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(this.articleUrl)}&t=${encodeURIComponent(this.articleTitle)}`, - twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(this.articleUrl)}&text=${encodeURIComponent(this.articleTitle)}&via=financialtimes`, - linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(this.articleUrl)}&title=${encodeURIComponent(this.articleTitle)}&source=Financial+Times`, - whatsapp: `whatsapp://send?text=${encodeURIComponent(this.articleTitle)}%20-%20${encodeURIComponent(this.articleUrl)}` - } : undefined; - - this.api = { - protocol: props.apiProtocol, - domain: props.apiDomain - } - } - - getDefault() { - const fundamentalProps = { - id: this.id, - title: this.title, - isFreeArticle: this.isFreeArticle, - isGiftUrlCreated: this.isGiftUrlCreated, - articleId: this.articleId, - articleUrl: this.articleUrl, - sessionId: this.sessionId, - showCopyButton: this.showCopyButton, - showShareButtons: this.showMobileShareLinks, - showNativeShareButton: this.showNativeShareButton, - mobileShareLinks: this.mobileShareLinks, - actionsRef: this.actionsRef, - }; - const additionalProps = this.isFreeArticle ? this.showNonGiftUrlSection() : this.showGiftUrlSection(); - - return { - ...fundamentalProps, - ...additionalProps - }; - } - - showGiftUrlSection() { - return { - shareType: SHARE_TYPE_GIFT, - url: this.urls.gift || this.urls.dummy, - urlType: this.urls.gift ? this.urlTypes.gift : this.urlTypes.dummy, - mailtoUrl: this.mailtoUrls.gift, - showCopyConfirmation: false - }; - } - - showNonGiftUrlSection() { - return { - shareType: SHARE_TYPE_NON_GIFT, - url: this.urls.nonGift, - urlType: this.urlTypes.nonGift, - mailtoUrl: this.mailtoUrls.nonGift, - showCopyConfirmation: false - }; - } - - setGiftUrl(url, limit, isShortened) { - this.urls.gift = url; - this.isGiftUrlCreated = true; - this.isGiftUrlShortened = isShortened; - this.mailtoUrls.gift = createMailtoUrl(this.articleTitle, url); - - return { - isGiftUrlCreated: this.isGiftUrlCreated, - url: this.urls.gift, - urlType: this.urlTypes.gift, - mailtoUrl: this.mailtoUrls.gift, - redemptionLimit: limit - }; - } - - setAllowance(giftCredits, monthlyAllowance, nextRenewalDate) { - const date = new Date(nextRenewalDate); - this.nextRenewalDateText = `${ monthNames[date.getMonth()] } ${ date.getDate() }`; - this.giftCredits = giftCredits; - this.monthlyAllowance = monthlyAllowance; - - return { - giftCredits: this.giftCredits, - monthlyAllowance: this.monthlyAllowance, - nextRenewalDateText: this.nextRenewalDateText - }; - } - - setShortenedNonGiftUrl(shortenedUrl) { - this.isNonGiftUrlShortened = true; - this.mailtoUrls.nonGift = createMailtoUrl(this.articleTitle, shortenedUrl); - this.urls.nonGift = shortenedUrl; - - return { - url: this.urls.nonGift, - mailtoUrl: this.mailtoUrls.nonGift - }; - } - - showCopyConfirmation() { - return { showCopyConfirmation: true }; - } - - hideCopyConfirmation() { - return { showCopyConfirmation: false }; - } -} diff --git a/components/x-gift-article/src/lib/updaters.js b/components/x-gift-article/src/lib/updaters.js new file mode 100644 index 000000000..e69de29bb From 79ee588d00b6b197b7da06a85f106fab82a226f9 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Mon, 5 Nov 2018 17:39:20 +0000 Subject: [PATCH 161/760] lolololol --- components/x-gift-article/package.json | 8 +-- components/x-gift-article/readme.md | 8 +-- components/x-gift-article/src/Buttons.jsx | 8 +-- components/x-gift-article/src/GiftArticle.jsx | 22 +++--- components/x-gift-article/src/Message.jsx | 6 +- .../src/RadioButtonsSection.jsx | 6 +- components/x-gift-article/src/Url.jsx | 4 +- components/x-gift-article/src/UrlSection.jsx | 4 +- .../x-gift-article/src/lib/constants.js | 4 +- components/x-gift-article/src/lib/updaters.js | 70 +++++++++++++++++++ components/x-gift-article/stories/index.js | 4 +- 11 files changed, 109 insertions(+), 35 deletions(-) diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 092a3a68c..8df2dda9a 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -2,9 +2,9 @@ "name": "@financial-times/x-gift-article", "version": "0.0.0", "description": "This module provides gift article form", - "main": "dist/GiftArticleWrapper.cjs.js", - "browser": "dist/GiftArticleWrapper.es5.js", - "module": "dist/GiftArticleWrapper.esm.js", + "main": "dist/GiftArticle.cjs.js", + "browser": "dist/GiftArticle.es5.js", + "module": "dist/GiftArticle.esm.js", "style": "dist/GiftArticle.css", "scripts": { "prepare": "npm run build", @@ -18,7 +18,7 @@ "license": "ISC", "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-interaction": "0.0.4", + "@financial-times/x-interaction": "0.0.5", "classnames": "^2.2.6" }, "devDependencies": { diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 6d8e49853..9cf666946 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -28,12 +28,12 @@ Component provided by this module expects a map of [gift article properties](#pr ```jsx import React from 'react'; -import { GiftArticleWrapper } from '@financial-times/x-gift-article'; +import { GiftArticle } from '@financial-times/x-gift-article'; // A == B == C -const a = GiftArticleWrapper(props); -const b = <GiftArticleWrapper {...props} />; -const c = React.createElement(GiftArticleWrapper, props); +const a = GiftArticle(props); +const b = <GiftArticle {...props} />; +const c = React.createElement(GiftArticle, props); ``` Your app should dispatch a custom event (`xDash.giftArticle.activate`) to activate the gift article form when your app actually displays the form. diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 3cba54ba7..f0a0d9690 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { shareType } from './lib/constants'; +import { ShareType } from './lib/constants'; import styles from './GiftArticle.css'; const ButtonsClassName = styles.buttons; @@ -31,7 +31,7 @@ export default ({ shareByNativeShare }) => { - if (isGiftUrlCreated || shareType === shareType.nonGift) { + if (isGiftUrlCreated || shareType === ShareType.nonGift) { if (showNativeShareButton) { return ( @@ -47,12 +47,12 @@ export default ({ <button className={ ButtonWithGapClassNames } type="button" - onClick={ shareType === shareType.gift ? copyGiftUrl : copyNonGiftUrl } + onClick={ shareType === ShareType.gift ? copyGiftUrl : copyNonGiftUrl } > Copy link </button> } - <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === shareType.gift ? emailGiftUrl : emailNonGiftUrl }>Email link</a> + <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === ShareType.gift ? emailGiftUrl : emailNonGiftUrl }>Email link</a> </div> ); } diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 582f31d63..1b2f7b66e 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -22,26 +22,30 @@ const withGiftFormActions = withActions( return { showGiftUrlSection() { - return updaters.showGiftUrlSection(); + return updaters.showGiftUrlSection; }, - async showNonGiftUrlSection() { - if (!updaters.isNonGiftUrlShortened) { - const { url, isShortened } = await api.getShorterUrl(updaters.urls.nonGift); + showNonGiftUrlSection() { + return async (state) => { + if (!state.isNonGiftUrlShortened) { + const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift); + if (isShortened) { - updaters.setShortenedNonGiftUrl(url); + return updaters.setShortenedNonGiftUrl(url)(state); } - } + } - return updaters.showNonGiftUrlSection(); + return updaters.showNonGiftUrlSection(state); + } }, async createGiftUrl() { - const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(articleId); + const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(props.articleId); if (redemptionUrl) { const { url, isShortened } = await api.getShorterUrl(redemptionUrl); tracking.createGiftLink(url, redemptionUrl); + return updaters.setGiftUrl(url, redemptionLimit, isShortened); } else { // TODO do something @@ -81,7 +85,7 @@ const withGiftFormActions = withActions( }, async activate() { - if (isFreeArticle) { + if (props.isFreeArticle) { const { url, isShortened } = await api.getShorterUrl(updaters.urls.nonGift); if (isShortened) { diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 9c45c522d..23852bf5b 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { shareType } from './lib/constants'; +import { ShareType } from './lib/constants'; import styles from './GiftArticle.css'; const messageClassName = styles.message; @@ -15,7 +15,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month ); } - if (shareType === shareType.gift) { + if (shareType === ShareType.gift) { if (giftCredits === 0) { return ( <div className={ messageClassName }> @@ -40,7 +40,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month ); } - if (shareType === shareType.nonGift) { + if (shareType === ShareType.nonGift) { return ( <div className={ messageClassName }> This link can only be read by existing subscribers diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 017443428..5b4c560ed 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { shareType } from './lib/constants'; +import { ShareType } from './lib/constants'; import styles from './GiftArticle.css'; const boldTextClassName = styles.bold; @@ -18,7 +18,7 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( value="giftLink" className="o-forms__radio" id="giftLink" - checked={ shareType === shareType.gift } + checked={ shareType === ShareType.gift } onChange={ showGiftUrlSection } /> @@ -32,7 +32,7 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( value="nonGiftLink" className="o-forms__radio" id="nonGiftLink" - checked={ shareType === shareType.nonGift } + checked={ shareType === ShareType.nonGift } onChange={ showNonGiftUrlSection }/> <label htmlFor="nonGiftLink" className="o-forms__label"> diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index d658229e4..a4fc6ec31 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { shareType.gift } from './lib/constants'; +import { ShareType } from './lib/constants'; import styles from './GiftArticle.css'; const urlClassNames = [ @@ -14,7 +14,7 @@ export default ({ shareType, isGiftUrlCreated, url, urlType }) => { name={ urlType } value={ url } className={ urlClassNames } - disabled={ shareType === shareType.gift && !isGiftUrlCreated } + disabled={ shareType === ShareType.gift && !isGiftUrlCreated } readOnly /> ); diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 959340055..3459513b2 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import { shareType.gift } from './lib/constants'; +import { ShareType } from './lib/constants'; import Url from './Url'; import Message from './Message'; import Buttons from './Buttons'; @@ -31,7 +31,7 @@ export default ({ shareByNativeShare }) => { - const hideUrlShareElements = ( giftCredits === 0 && shareType === shareType.gift ); + const hideUrlShareElements = ( giftCredits === 0 && shareType === ShareType.gift ); const showUrlShareElements = !hideUrlShareElements; return ( diff --git a/components/x-gift-article/src/lib/constants.js b/components/x-gift-article/src/lib/constants.js index 19aea63ba..dc8ff8d49 100644 --- a/components/x-gift-article/src/lib/constants.js +++ b/components/x-gift-article/src/lib/constants.js @@ -1,9 +1,9 @@ -export const shareType = { +export const ShareType = { gift: 'gift', nonGift: 'nonGift' }; -export const urlType = { +export const UrlType = { dummy: 'example-gift-link', gift: 'gift-link', nonGift: 'non-gift-link' diff --git a/components/x-gift-article/src/lib/updaters.js b/components/x-gift-article/src/lib/updaters.js index e69de29bb..e7fc5fd4e 100644 --- a/components/x-gift-article/src/lib/updaters.js +++ b/components/x-gift-article/src/lib/updaters.js @@ -0,0 +1,70 @@ +import { createMailtoUrl } from './share-link-actions'; +import { ShareType, UrlType } from './constants'; + +const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + +export const showGiftUrlSection = (props) => ({ + shareType: ShareType.gift, + url: props.urls.gift || props.urls.dummy, + urlType: props.urls.gift ? UrlType.gift : UrlType.dummy, + mailtoUrl: props.mailtoUrls.gift, + showCopyConfirmation: false +}); + +export const showNonGiftUrlSection = (props) => ({ + shareType: ShareType.nonGift, + url: props.urls.nonGift, + urlType: UrlType.nonGift, + mailtoUrl: props.mailtoUrls.nonGift, + showCopyConfirmation: false +}); + +export const setGiftUrl = (url, redemptionLimit, isShortened) => { + const mailtoUrl = createMailtoUrl(this.articleTitle, url); + + return props => ({ + url, + mailtoUrl, + redemptionLimit, + isGiftUrlCreated: true, + isGiftUrlShortened: isShortened, + urlType: UrlType.gift, + + urls: Object.assign(props.urls, { + gift: url, + }), + + mailtoUrls: Object.assign(props.mailtoUrls, { + gift: mailtoUrl, + }) + }); +}; + +export const setAllowance = (giftCredits, monthlyAllowance, nextRenewalDate) => { + const date = new Date(nextRenewalDate); + const nextRenewalDateText = `${ monthNames[date.getMonth()] } ${ date.getDate() }`; + + return { + giftCredits, + monthlyAllowance, + nextRenewalDateText + }; +}; + +export const setShortenedNonGiftUrl = (shortenedUrl) => props => { + const mailtoUrl = createMailtoUrl(props.articleTitle, shortenedUrl); + + return { + url: shortenedUrl, + mailtoUrl: mailtoUrl, + isNonGiftUrlShortened: true, + + urls: Object.assign(props.urls, { + gift: shortenedUrl, + }), + + mailtoUrls: Object.assign(props.mailtoUrls, { + gift: mailtoUrl, + }) + }; +}; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 873264c8e..1acfd379d 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -1,6 +1,6 @@ -const { GiftArticleWrapper } = require('../'); +const { GiftArticle } = require('../'); -exports.component = GiftArticleWrapper; +exports.component = GiftArticle; exports.package = require('../package.json'); From 80abce8a678696542330c3d914c486ba3ceabbb2 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Tue, 6 Nov 2018 11:56:22 +0000 Subject: [PATCH 162/760] test fixes --- .../__snapshots__/snapshots.test.js.snap | 6 +- components/x-gift-article/src/Buttons.jsx | 15 +- components/x-gift-article/src/Form.jsx | 8 +- components/x-gift-article/src/GiftArticle.jsx | 132 ++++++++++-------- components/x-gift-article/src/UrlSection.jsx | 16 +-- components/x-gift-article/src/lib/updaters.js | 8 +- 6 files changed, 93 insertions(+), 92 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 9e367299c..41504300b 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -174,7 +174,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g <a className="o-share__icon o-share__icon--facebook MobileShareButtons_facebook__1ji2o" data-trackable="facebook" - href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" + href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" rel="noopener" > Facebook @@ -192,7 +192,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g <a className="o-share__icon o-share__icon--twitter MobileShareButtons_twitter__1QRsw" data-trackable="twitter" - href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" + href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" rel="noopener" > Twitter @@ -210,7 +210,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g <a className="o-share__icon o-share__icon--linkedin MobileShareButtons_linkedin__1-2-Y" data-trackable="linkedin" - href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" + href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" rel="noopener" > LinkedIn diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index f0a0d9690..08bf9394a 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -21,14 +21,9 @@ export default ({ shareType, isGiftUrlCreated, mailtoUrl, - createGiftUrl, - copyGiftUrl, - copyNonGiftUrl, - emailGiftUrl, - emailNonGiftUrl, showCopyButton, showNativeShareButton, - shareByNativeShare + actions }) => { if (isGiftUrlCreated || shareType === ShareType.nonGift) { @@ -36,7 +31,7 @@ export default ({ if (showNativeShareButton) { return ( <div className={ ButtonsClassName }> - <button className={ ButtonWithGapClassNames } type="button" onClick={ shareByNativeShare }>Share link</button> + <button className={ ButtonWithGapClassNames } type="button" onClick={ actions.shareByNativeShare }>Share link</button> </div> ); } @@ -47,19 +42,19 @@ export default ({ <button className={ ButtonWithGapClassNames } type="button" - onClick={ shareType === ShareType.gift ? copyGiftUrl : copyNonGiftUrl } + onClick={ shareType === ShareType.gift ? actions.copyGiftUrl : actions.copyNonGiftUrl } > Copy link </button> } - <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === ShareType.gift ? emailGiftUrl : emailNonGiftUrl }>Email link</a> + <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === ShareType.gift ? actions.emailGiftUrl : actions.emailNonGiftUrl }>Email link</a> </div> ); } return ( <div className={ ButtonsClassName }> - <button className={ ButtonClassNames } type="button" onClick={ createGiftUrl }> + <button className={ ButtonClassNames } type="button" onClick={ actions.createGiftUrl }> Create gift link </button> </div> diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index be34ffb45..5a46e12f2 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -18,9 +18,9 @@ export default (props) => ( <Title title={ props.title }/> { !props.isFreeArticle && <RadioButtonsSection - shareType={ props.shareType } - showGiftUrlSection={ props.actions.showGiftUrlSection } - showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> + shareType={ props.shareType } + showGiftUrlSection={ props.actions.showGiftUrlSection } + showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> } <UrlSection {...props} /> @@ -30,7 +30,7 @@ export default (props) => ( { props.showCopyConfirmation && <CopyConfirmation hideCopyConfirmation={ props.actions.hideCopyConfirmation }/> } - { props.showShareButtons && + { props.showMobileShareLinks && <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> } </div> ); diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 1b2f7b66e..e9357d662 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -14,10 +14,10 @@ const isCopySupported = typeof document !== 'undefined' && document.queryCommandSupported('copy'); const withGiftFormActions = withActions( - props => { + initialProps => { const api = new ApiClient({ - protocol: props.apiProtocol, - domain: props.apiDomain + protocol: initialProps.apiProtocol, + domain: initialProps.apiDomain }); return { @@ -40,7 +40,7 @@ const withGiftFormActions = withActions( }, async createGiftUrl() { - const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(props.articleId); + const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(initialProps.articleId); if (redemptionUrl) { const { url, isShortened } = await api.getShorterUrl(redemptionUrl); @@ -53,27 +53,37 @@ const withGiftFormActions = withActions( }, copyGiftUrl(event) { - const giftUrl = updaters.urls.gift; copyToClipboard(event); - tracking.copyLink('giftLink', giftUrl); - return { showCopyConfirmation: true }; + return state => { + const giftUrl = state.urls.gift; + tracking.copyLink('giftLink', giftUrl); + + return { showCopyConfirmation: true }; + }; }, copyNonGiftUrl(event) { - const nonGiftUrl = updaters.urls.nonGift; copyToClipboard(event); - tracking.copyLink('nonGiftLink', nonGiftUrl); - return { showCopyConfirmation: true }; + return state => { + const nonGiftUrl = state.urls.nonGift; + tracking.copyLink('nonGiftLink', nonGiftUrl); + + return { showCopyConfirmation: true }; + } }, emailGiftUrl() { - tracking.emailLink('giftLink', updaters.urls.gift); + return state => { + tracking.emailLink('giftLink', state.urls.gift); + }; }, emailNonGiftUrl() { - tracking.emailLink('nonGiftLink', updaters.urls.nonGift); + return state => { + tracking.emailLink('nonGiftLink', state.urls.nonGift); + }; }, hideCopyConfirmation() { @@ -84,60 +94,66 @@ const withGiftFormActions = withActions( throw new Error(`shareByNativeShare should be implemented by x-gift-article's consumers`); }, - async activate() { - if (props.isFreeArticle) { - const { url, isShortened } = await api.getShorterUrl(updaters.urls.nonGift); - - if (isShortened) { - return updaters.setShortenedNonGiftUrl(url); - } - } else { - const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); + activate() { + return async state => { + if (initialProps.isFreeArticle) { + const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift); - // avoid to use giftCredits >= 0 because it returns true when null and "" - if (giftCredits > 0 || giftCredits === 0) { - return updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); + if (isShortened) { + return updaters.setShortenedNonGiftUrl(url)(state); + } } else { - // TODO do something + const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); + + // avoid to use giftCredits >= 0 because it returns true when null and "" + if (giftCredits > 0 || giftCredits === 0) { + return updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); + } else { + // TODO do something + } } } } } }, - props => ({ - title: 'Share this article', - giftCredits: undefined, - monthlyAllowance: undefined, - showCopyButton: isCopySupported, - isGiftUrlCreated: false, - isGiftUrlShortened: false, - isNonGiftUrlShortened: false, - - urls: { - dummy: 'https://on.ft.com/gift_link', - gift: undefined, - nonGift: `${props.articleUrl}?shareType=nongift` - }, - - mailtoUrls: { - gift: undefined, - nonGift: createMailtoUrl(props.articleTitle, `${props.articleUrl}?shareType=nongift`) - }, - - mobileShareLinks: props.showMobileShareLinks - ? { - facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(props.articleUrl)}&t=${encodeURIComponent(props.articleTitle)}`, - twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(props.articleUrl)}&text=${encodeURIComponent(props.articleTitle)}&via=financialtimes`, - linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(props.articleUrl)}&title=${encodeURIComponent(props.articleTitle)}&source=Financial+Times`, - whatsapp: `whatsapp://send?text=${encodeURIComponent(props.articleTitle)}%20-%20${encodeURIComponent(props.articleUrl)}` - } - : undefined, + props => { + const initialState = { + title: 'Share this article', + giftCredits: undefined, + monthlyAllowance: undefined, + showCopyButton: isCopySupported, + isGiftUrlCreated: false, + isGiftUrlShortened: false, + isNonGiftUrlShortened: false, + + urls: { + dummy: 'https://on.ft.com/gift_link', + gift: undefined, + nonGift: `${props.articleUrl}?shareType=nongift` + }, + + mailtoUrls: { + gift: undefined, + nonGift: createMailtoUrl(props.articleTitle, `${props.articleUrl}?shareType=nongift`) + }, + + mobileShareLinks: props.showMobileShareLinks + ? { + facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(props.articleUrl)}&t=${encodeURIComponent(props.articleTitle)}`, + twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(props.articleUrl)}&text=${encodeURIComponent(props.articleTitle)}&via=financialtimes`, + linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(props.articleUrl)}&title=${encodeURIComponent(props.articleTitle)}&source=Financial+Times`, + whatsapp: `whatsapp://send?text=${encodeURIComponent(props.articleTitle)}%20-%20${encodeURIComponent(props.articleUrl)}` + } + : undefined + }; + + const expandedProps = Object.assign({}, props, initialState); + const sectionProps = props.isFreeArticle + ? updaters.showNonGiftUrlSection(expandedProps) + : updaters.showGiftUrlSection(expandedProps); - ...(props.isFreeArticle - ? updaters.showNonGiftUrlSection(props) - : updaters.showGiftUrlSection(props) - ), - }) + return Object.assign(initialState, sectionProps); + } ); const BaseGiftArticle = (props) => { diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 3459513b2..7c0a8baf9 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -20,15 +20,10 @@ export default ({ monthlyAllowance, nextRenewalDateText, mailtoUrl, - createGiftUrl, - copyGiftUrl, - copyNonGiftUrl, - emailGiftUrl, - emailNonGiftUrl, redemptionLimit, showCopyButton, showNativeShareButton, - shareByNativeShare + actions }) => { const hideUrlShareElements = ( giftCredits === 0 && shareType === ShareType.gift ); @@ -61,15 +56,10 @@ export default ({ shareType, isGiftUrlCreated, mailtoUrl, - createGiftUrl, - copyGiftUrl, - copyNonGiftUrl, - emailGiftUrl, - emailNonGiftUrl, showCopyButton, showNativeShareButton, - shareByNativeShare, - }} />} + actions, + }} /> } </div> ); diff --git a/components/x-gift-article/src/lib/updaters.js b/components/x-gift-article/src/lib/updaters.js index e7fc5fd4e..8732ad66c 100644 --- a/components/x-gift-article/src/lib/updaters.js +++ b/components/x-gift-article/src/lib/updaters.js @@ -19,10 +19,10 @@ export const showNonGiftUrlSection = (props) => ({ showCopyConfirmation: false }); -export const setGiftUrl = (url, redemptionLimit, isShortened) => { - const mailtoUrl = createMailtoUrl(this.articleTitle, url); +export const setGiftUrl = (url, redemptionLimit, isShortened) => props => { + const mailtoUrl = createMailtoUrl(props.articleTitle, url); - return props => ({ + return { url, mailtoUrl, redemptionLimit, @@ -37,7 +37,7 @@ export const setGiftUrl = (url, redemptionLimit, isShortened) => { mailtoUrls: Object.assign(props.mailtoUrls, { gift: mailtoUrl, }) - }); + }; }; export const setAllowance = (giftCredits, monthlyAllowance, nextRenewalDate) => { From 7080070189bd28734bcb58eb1fd7b4fa54c2418e Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Tue, 6 Nov 2018 12:17:24 +0000 Subject: [PATCH 163/760] properly collate state updates for section switch --- components/x-gift-article/src/GiftArticle.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index e9357d662..94c41f21d 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -27,15 +27,20 @@ const withGiftFormActions = withActions( showNonGiftUrlSection() { return async (state) => { + const update = updaters.showNonGiftUrlSection(state); + if (!state.isNonGiftUrlShortened) { const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift); if (isShortened) { - return updaters.setShortenedNonGiftUrl(url)(state); + Object.assign( + update, + updaters.setShortenedNonGiftUrl(url)(state) + ); } } - return updaters.showNonGiftUrlSection(state); + return update; } }, From ab5d47b02ce52f30ae73dff97445b3aa87c776a9 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Tue, 6 Nov 2018 12:55:58 +0000 Subject: [PATCH 164/760] release version of x-interaction --- components/x-gift-article/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 8df2dda9a..5747a8280 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -18,7 +18,7 @@ "license": "ISC", "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-interaction": "0.0.5", + "@financial-times/x-interaction": "file:../x-interaction", "classnames": "^2.2.6" }, "devDependencies": { From 6f9fb59c86111e78ac4630210705e4ca70d809f3 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Tue, 6 Nov 2018 14:45:46 +0000 Subject: [PATCH 165/760] sessionId no longer used --- components/x-gift-article/readme.md | 15 +++++++-------- components/x-gift-article/stories/native-share.js | 1 - .../x-gift-article/stories/with-gift-credits.js | 1 - 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 9cf666946..fe2cba718 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -14,12 +14,12 @@ npm install --save @financial-times/x-gift-article ## Styling -To get correct styling, Your app should have origami components below. -[o-fonts](https://registry.origami.ft.com/components/o-fonts) -[o-buttons](https://registry.origami.ft.com/components/o-buttons) -[o-forms](https://registry.origami.ft.com/components/o-forms) -[o-loading](https://registry.origami.ft.com/components/o-loading) -[o-share](https://registry.origami.ft.com/components/o-share) +To get correct styling, Your app should have origami components below. +[o-fonts](https://registry.origami.ft.com/components/o-fonts) +[o-buttons](https://registry.origami.ft.com/components/o-buttons) +[o-forms](https://registry.origami.ft.com/components/o-forms) +[o-loading](https://registry.origami.ft.com/components/o-loading) +[o-share](https://registry.origami.ft.com/components/o-share) [o-message](https://registry.origami.ft.com/components/o-message) ## Usage @@ -36,7 +36,7 @@ const b = <GiftArticle {...props} />; const c = React.createElement(GiftArticle, props); ``` -Your app should dispatch a custom event (`xDash.giftArticle.activate`) to activate the gift article form when your app actually displays the form. +Your app should dispatch a custom event (`xDash.giftArticle.activate`) to activate the gift article form when your app actually displays the form. `document.body.dispatchEvent(new CustomEvent('xDash.giftArticle.activate'));` All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. @@ -52,7 +52,6 @@ Property | Type | Required | Note `articleUrl` | String | yes | Canonical URL `articleTitle` | String | yes | `articleId` | String | yes | Content UUID -`sessionId` | String | yes | This is needed to get a gift url. `showMobileShareLinks` | Boolean | no | `nativeShare` | Boolean | no | This is a property for App to display Native Sharing. `apiProtocol` | String | no | The protocol to use when making requests to the gift article and URL shortening services. Ignored if `apiDomain` is not set. diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/stories/native-share.js index 377fe05f9..a42fe98e9 100644 --- a/components/x-gift-article/stories/native-share.js +++ b/components/x-gift-article/stories/native-share.js @@ -11,7 +11,6 @@ exports.data = { articleUrl, articleTitle: 'Title Title Title Title', articleId, - sessionId: 'session id', nativeShare: true, id: 'base-gift-article-static-id' }; diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 1b592664d..85e98e091 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -11,7 +11,6 @@ exports.data = { articleUrl, articleTitle: 'Title Title Title Title', articleId, - sessionId: 'session id', showMobileShareLinks: true, id: 'base-gift-article-static-id' }; From 0f0a9da10763f07b8530c00d53821a9da39442dc Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Tue, 6 Nov 2018 15:51:15 +0000 Subject: [PATCH 166/760] rationalise some props, update docs --- components/x-gift-article/readme.md | 42 +++++++++++++++---- components/x-gift-article/src/Buttons.jsx | 5 +-- components/x-gift-article/src/GiftArticle.jsx | 14 +++---- components/x-gift-article/src/UrlSection.jsx | 4 +- components/x-gift-article/src/lib/api.js | 1 - components/x-gift-article/src/lib/updaters.js | 4 +- .../x-gift-article/stories/free-article.js | 8 ++-- .../x-gift-article/stories/native-share.js | 8 ++-- .../stories/with-gift-credits.js | 8 ++-- .../stories/without-gift-credits.js | 8 ++-- 10 files changed, 67 insertions(+), 35 deletions(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index fe2cba718..b40940d10 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -10,8 +10,6 @@ This module is compatible with Node 6+ and is distributed on npm. npm install --save @financial-times/x-gift-article ``` -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine - ## Styling To get correct styling, Your app should have origami components below. @@ -36,22 +34,50 @@ const b = <GiftArticle {...props} />; const c = React.createElement(GiftArticle, props); ``` -Your app should dispatch a custom event (`xDash.giftArticle.activate`) to activate the gift article form when your app actually displays the form. -`document.body.dispatchEvent(new CustomEvent('xDash.giftArticle.activate'));` +Your app should trigger the `activate` action to activate the gift article form when your app actually displays the form. For example, if your app is client-side rendered, you can use `actionsRef` to trigger this action: + +```jsx +import { h, Component } from '@financial-times/x-engine'; +import { GiftArticle } from '@financial-times/x-gift-article'; + +class Container extends Component { + showGiftArticle() { + if(this.giftArticleActions) { + this.setState({ showGiftArticle: true }); + + // trigger the action + this.giftArticleActions.activate(); + } + } + + render() { + return <div> + <button onClick={() => this.showGiftArticle()}> + Share + </button> + + <div style={{display: this.state.showGiftArticle ? 'block' : 'none'}}> + <GiftArticle {...this.props} actionsRef={actions => this.giftArticleActions = actions} /> + </div> + </div> + } +} +``` + +For more information about triggering actions, see the [x-interaction documentation][interaction]. All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. [jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ - +[interaction]: /components/x-interaction#triggering-actions-externally +[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine ### Properties Property | Type | Required | Note --------------------------|---------|----------|---- `isFreeArticle` | Boolean | yes | Only non gift form is displayed when this value is `true`. -`articleUrl` | String | yes | Canonical URL -`articleTitle` | String | yes | -`articleId` | String | yes | Content UUID +`article` | Object | yes | Must contain `id`, `title` and `url` properties `showMobileShareLinks` | Boolean | no | `nativeShare` | Boolean | no | This is a property for App to display Native Sharing. `apiProtocol` | String | no | The protocol to use when making requests to the gift article and URL shortening services. Ignored if `apiDomain` is not set. diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 08bf9394a..8f5611aa7 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -16,19 +16,18 @@ const ButtonWithGapClassNames = [ styles['button--with-gap'] ].join(' '); - export default ({ shareType, isGiftUrlCreated, mailtoUrl, showCopyButton, - showNativeShareButton, + nativeShare, actions }) => { if (isGiftUrlCreated || shareType === ShareType.nonGift) { - if (showNativeShareButton) { + if (nativeShare) { return ( <div className={ ButtonsClassName }> <button className={ ButtonWithGapClassNames } type="button" onClick={ actions.shareByNativeShare }>Share link</button> diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 94c41f21d..9ee016343 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -45,7 +45,7 @@ const withGiftFormActions = withActions( }, async createGiftUrl() { - const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(initialProps.articleId); + const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(initialProps.article.id); if (redemptionUrl) { const { url, isShortened } = await api.getShorterUrl(redemptionUrl); @@ -134,20 +134,20 @@ const withGiftFormActions = withActions( urls: { dummy: 'https://on.ft.com/gift_link', gift: undefined, - nonGift: `${props.articleUrl}?shareType=nongift` + nonGift: `${props.article.url}?shareType=nongift` }, mailtoUrls: { gift: undefined, - nonGift: createMailtoUrl(props.articleTitle, `${props.articleUrl}?shareType=nongift`) + nonGift: createMailtoUrl(props.article.title, `${props.article.url}?shareType=nongift`) }, mobileShareLinks: props.showMobileShareLinks ? { - facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(props.articleUrl)}&t=${encodeURIComponent(props.articleTitle)}`, - twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(props.articleUrl)}&text=${encodeURIComponent(props.articleTitle)}&via=financialtimes`, - linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(props.articleUrl)}&title=${encodeURIComponent(props.articleTitle)}&source=Financial+Times`, - whatsapp: `whatsapp://send?text=${encodeURIComponent(props.articleTitle)}%20-%20${encodeURIComponent(props.articleUrl)}` + facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(props.article.url)}&t=${encodeURIComponent(props.article.title)}`, + twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(props.article.url)}&text=${encodeURIComponent(props.article.title)}&via=financialtimes`, + linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(props.article.url)}&title=${encodeURIComponent(props.article.title)}&source=Financial+Times`, + whatsapp: `whatsapp://send?text=${encodeURIComponent(props.article.title)}%20-%20${encodeURIComponent(props.article.url)}` } : undefined }; diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 7c0a8baf9..dbaba6dcd 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -22,7 +22,7 @@ export default ({ mailtoUrl, redemptionLimit, showCopyButton, - showNativeShareButton, + nativeShare, actions }) => { @@ -57,7 +57,7 @@ export default ({ isGiftUrlCreated, mailtoUrl, showCopyButton, - showNativeShareButton, + nativeShare, actions, }} /> } diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index e10425d73..0b5594660 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -74,4 +74,3 @@ export default class ApiClient { return { url, isShortened }; } } - diff --git a/components/x-gift-article/src/lib/updaters.js b/components/x-gift-article/src/lib/updaters.js index 8732ad66c..c91e4365a 100644 --- a/components/x-gift-article/src/lib/updaters.js +++ b/components/x-gift-article/src/lib/updaters.js @@ -20,7 +20,7 @@ export const showNonGiftUrlSection = (props) => ({ }); export const setGiftUrl = (url, redemptionLimit, isShortened) => props => { - const mailtoUrl = createMailtoUrl(props.articleTitle, url); + const mailtoUrl = createMailtoUrl(props.article.title, url); return { url, @@ -52,7 +52,7 @@ export const setAllowance = (giftCredits, monthlyAllowance, nextRenewalDate) => }; export const setShortenedNonGiftUrl = (shortenedUrl) => props => { - const mailtoUrl = createMailtoUrl(props.articleTitle, shortenedUrl); + const mailtoUrl = createMailtoUrl(props.article.title, shortenedUrl); return { url: shortenedUrl, diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index 11c58a67a..29d66757c 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -6,9 +6,11 @@ exports.title = 'Free article'; exports.data = { title: 'Share this article (free)', isFreeArticle: true, - articleUrl, - articleTitle: 'Title Title Title Title', - id: 'base-gift-article-static-id' + article: { + title: 'Title Title Title Title', + id: 'base-gift-article-static-id', + url: articleUrl, + }, }; // This reference is only required for hot module loading in development diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/stories/native-share.js index a42fe98e9..a1d901930 100644 --- a/components/x-gift-article/stories/native-share.js +++ b/components/x-gift-article/stories/native-share.js @@ -8,9 +8,11 @@ exports.title = 'With native share on App'; exports.data = { title: 'Share this article (on App)', isFreeArticle: false, - articleUrl, - articleTitle: 'Title Title Title Title', - articleId, + article: { + id: articleId, + url: articleUrl, + title: 'Title Title Title Title', + }, nativeShare: true, id: 'base-gift-article-static-id' }; diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 85e98e091..cd25ac520 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -8,9 +8,11 @@ exports.title = 'With gift credits'; exports.data = { title: 'Share this article (with credit)', isFreeArticle: false, - articleUrl, - articleTitle: 'Title Title Title Title', - articleId, + article: { + id: articleId, + url: articleUrl, + title: 'Title Title Title Title', + }, showMobileShareLinks: true, id: 'base-gift-article-static-id' }; diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index 0a94e0a50..7e4b97221 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -6,9 +6,11 @@ exports.title = 'Without gift credits'; exports.data = { title: 'Share this article (without credit)', isFreeArticle: false, - articleUrl, - articleTitle: 'Title Title Title Title', - id: 'base-gift-article-static-id' + article: { + id: 'article id', + url: articleUrl, + title: 'Title Title Title Title', + }, }; // This reference is only required for hot module loading in development From 9ca786b551ef9c063627566ef4981491001cee11 Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Thu, 29 Nov 2018 16:22:32 +0000 Subject: [PATCH 167/760] Simple error handling for bad API response --- components/x-gift-article/src/Buttons.jsx | 8 +++-- components/x-gift-article/src/Message.jsx | 8 +++++ .../x-gift-article/stories/error-response.js | 32 +++++++++++++++++++ components/x-gift-article/stories/index.js | 3 +- 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 components/x-gift-article/stories/error-response.js diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 8f5611aa7..e6954f8fd 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -22,7 +22,8 @@ export default ({ mailtoUrl, showCopyButton, nativeShare, - actions + actions, + giftCredits }) => { if (isGiftUrlCreated || shareType === ShareType.nonGift) { @@ -30,7 +31,7 @@ export default ({ if (nativeShare) { return ( <div className={ ButtonsClassName }> - <button className={ ButtonWithGapClassNames } type="button" onClick={ actions.shareByNativeShare }>Share link</button> + <button className={ ButtonWithGapClassNames } disabled={ !giftCredits } type="button" onClick={ actions.shareByNativeShare }>Share link</button> </div> ); } @@ -40,6 +41,7 @@ export default ({ { showCopyButton && <button className={ ButtonWithGapClassNames } + disabled={ !giftCredits } type="button" onClick={ shareType === ShareType.gift ? actions.copyGiftUrl : actions.copyNonGiftUrl } > @@ -53,7 +55,7 @@ export default ({ return ( <div className={ ButtonsClassName }> - <button className={ ButtonClassNames } type="button" onClick={ actions.createGiftUrl }> + <button className={ ButtonClassNames } disabled={ !giftCredits } type="button" onClick={ actions.createGiftUrl }> Create gift link </button> </div> diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 23852bf5b..50cdbc202 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -33,6 +33,14 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month ); } + if (!giftCredits) { + return ( + <div className={ messageClassName }> + Unable to fetch gift credits. Please try again later + </div> + ); + } + return ( <div className={ messageClassName }> You have <span className={ boldTextClassName }>{ giftCredits } gift article { giftCredits === 1 ? 'credit' : 'credits' }</span> left this month diff --git a/components/x-gift-article/stories/error-response.js b/components/x-gift-article/stories/error-response.js new file mode 100644 index 000000000..6bc7a8c2c --- /dev/null +++ b/components/x-gift-article/stories/error-response.js @@ -0,0 +1,32 @@ +const articleUrl = 'https://www.ft.com/content/blahblahblah'; +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; + +exports.title = 'With a bad response from membership APIs'; + +exports.data = { + title: 'Share this article (unable to fetch credits)', + isFreeArticle: false, + article: { + id: 'article id', + url: articleUrl, + title: 'Title Title Title Title', + }, +}; + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module; + +exports.fetchMock = fetchMock => { + fetchMock + .get( + '/article-email/credits', + { + throw: new Error('bad membership api') + } + ) + .get( + `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, + { shortenedUrl: 'https://shortened-non-gift-url' } + ); +}; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 1acfd379d..f4acbea05 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -17,5 +17,6 @@ exports.stories = [ require('./with-gift-credits'), require('./without-gift-credits'), require('./free-article'), - require('./native-share') + require('./native-share'), + require('./error-response') ]; From aa8aa2829c9159c9fc42bcb0fdaeb468f3a1b86c Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Thu, 29 Nov 2018 16:25:46 +0000 Subject: [PATCH 168/760] Only disable Create button --- components/x-gift-article/src/Buttons.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index e6954f8fd..6fe19e8b6 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -31,7 +31,7 @@ export default ({ if (nativeShare) { return ( <div className={ ButtonsClassName }> - <button className={ ButtonWithGapClassNames } disabled={ !giftCredits } type="button" onClick={ actions.shareByNativeShare }>Share link</button> + <button className={ ButtonWithGapClassNames } type="button" onClick={ actions.shareByNativeShare }>Share link</button> </div> ); } @@ -41,7 +41,6 @@ export default ({ { showCopyButton && <button className={ ButtonWithGapClassNames } - disabled={ !giftCredits } type="button" onClick={ shareType === ShareType.gift ? actions.copyGiftUrl : actions.copyNonGiftUrl } > From cf46fc570e021b8a26541d347e16d3619eeea2d1 Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Thu, 29 Nov 2018 16:49:22 +0000 Subject: [PATCH 169/760] Update Stories to use correct URLs --- components/x-gift-article/src/UrlSection.jsx | 1 + components/x-gift-article/src/lib/api.js | 8 ++++---- components/x-gift-article/stories/error-response.js | 2 +- components/x-gift-article/stories/free-article.js | 2 +- components/x-gift-article/stories/native-share.js | 2 +- components/x-gift-article/stories/with-gift-credits.js | 2 +- components/x-gift-article/stories/without-gift-credits.js | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index dbaba6dcd..2c190f1f5 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -59,6 +59,7 @@ export default ({ showCopyButton, nativeShare, actions, + giftCredits }} /> } </div> diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 0b5594660..74ad573d3 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -28,12 +28,12 @@ export default class ApiClient { async getGiftArticleAllowance() { try { - const json = await this.fetchJson('/article/gift-credits'); + const { credits } = await this.fetchJson('/article/gift-credits'); return { - monthlyAllowance: json.allowance, - giftCredits: json.remainingCredits, - nextRenewalDate: json.renewalDate + monthlyAllowance: credits.allowance, + giftCredits: credits.remainingCredits, + nextRenewalDate: credits.renewalDate }; } catch (e) { return { monthlyAllowance: undefined, giftCredits: undefined, nextRenewalDate: undefined }; diff --git a/components/x-gift-article/stories/error-response.js b/components/x-gift-article/stories/error-response.js index 6bc7a8c2c..c7e96547b 100644 --- a/components/x-gift-article/stories/error-response.js +++ b/components/x-gift-article/stories/error-response.js @@ -20,7 +20,7 @@ exports.m = module; exports.fetchMock = fetchMock => { fetchMock .get( - '/article-email/credits', + '/article/gift-credits', { throw: new Error('bad membership api') } diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index 29d66757c..9fa3b8452 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -20,7 +20,7 @@ exports.m = module; exports.fetchMock = fetchMock => { fetchMock .get( - '/article-email/credits', + '/article/gift-credits', { 'credits': { 'allowance': 20, diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/stories/native-share.js index a1d901930..210ec5edb 100644 --- a/components/x-gift-article/stories/native-share.js +++ b/components/x-gift-article/stories/native-share.js @@ -24,7 +24,7 @@ exports.m = module; exports.fetchMock = fetchMock => { fetchMock .get( - '/article-email/credits', + '/article/gift-credits', { 'credits': { 'allowance': 20, diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index cd25ac520..01292997e 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -24,7 +24,7 @@ exports.m = module; exports.fetchMock = fetchMock => { fetchMock .get( - '/article-email/credits', + '/article/gift-credits', { 'credits': { 'allowance': 20, diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index 7e4b97221..494c76040 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -20,7 +20,7 @@ exports.m = module; exports.fetchMock = fetchMock => { fetchMock .get( - '/article-email/credits', + '/article/gift-credits', { 'credits': { 'allowance': 20, From e03d7e0a4ef5f6b77ce01b1c23ea2bc7528b6707 Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Thu, 29 Nov 2018 16:57:59 +0000 Subject: [PATCH 170/760] Only display error message when response has failed --- components/x-gift-article/src/GiftArticle.jsx | 2 +- components/x-gift-article/src/Message.jsx | 4 ++-- components/x-gift-article/src/UrlSection.jsx | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 9ee016343..3dba859bb 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -114,7 +114,7 @@ const withGiftFormActions = withActions( if (giftCredits > 0 || giftCredits === 0) { return updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); } else { - // TODO do something + return { invalidResponseFromApi: true } } } } diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 50cdbc202..831c9d1a4 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -5,7 +5,7 @@ import styles from './GiftArticle.css'; const messageClassName = styles.message; const boldTextClassName = styles.bold; -export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, nextRenewalDateText, redemptionLimit }) => { +export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, nextRenewalDateText, redemptionLimit, invalidResponseFromApi }) => { if (isFreeArticle) { return ( @@ -33,7 +33,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month ); } - if (!giftCredits) { + if (invalidResponseFromApi) { return ( <div className={ messageClassName }> Unable to fetch gift credits. Please try again later diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 2c190f1f5..ee8da1c6f 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -23,6 +23,7 @@ export default ({ redemptionLimit, showCopyButton, nativeShare, + invalidResponseFromApi, actions }) => { @@ -50,6 +51,7 @@ export default ({ monthlyAllowance, nextRenewalDateText, redemptionLimit, + invalidResponseFromApi, }} /> { showUrlShareElements && <Buttons {...{ From 7f8eb3e1d86d96a57c7b7246ede605dc6982caac Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Fri, 30 Nov 2018 09:25:34 +0000 Subject: [PATCH 171/760] Update snapshot reflecting initial disabled state --- .../__snapshots__/snapshots.test.js.snap | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 41504300b..b549f95da 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -58,6 +58,109 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a </div> `; +exports[`@financial-times/x-gift-article renders a default With a bad response from membership APIs x-gift-article 1`] = ` +<div + className="GiftArticle_container__nGwU_" +> + <form + name="gift-form" + > + <fieldset + className="o-forms GiftArticle_form__lC3qs" + > + <div + className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + > + Share this article (unable to fetch credits) + </div> + <div + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + > + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> + <label + className="o-forms__label" + htmlFor="giftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + anyone + </span> + (uses 1 gift credit) + </label> + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> + <label + className="o-forms__label" + htmlFor="nonGiftLink" + > + with + <span + className="GiftArticle_bold__Ys2Sp" + > + other FT subscribers + </span> + </label> + </div> + <div + className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" + data-section-id="giftLink" + data-trackable="giftLink" + > + <input + className="o-forms__text GiftArticle_url__17SKH" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + <div + className="GiftArticle_message__2zqH2" + > + You have + <span + className="GiftArticle_bold__Ys2Sp" + > + gift article + credits + </span> + left this month + </div> + <div + className="GiftArticle_buttons__SB7ql" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + disabled={true} + onClick={[Function]} + type="button" + > + Create gift link + </button> + </div> + </div> + </fieldset> + </form> +</div> +`; + exports[`@financial-times/x-gift-article renders a default With gift credits x-gift-article 1`] = ` <div className="GiftArticle_container__nGwU_" @@ -148,6 +251,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g > <button className="o-buttons o-buttons--primary o-buttons--big" + disabled={true} onClick={[Function]} type="button" > @@ -334,6 +438,7 @@ exports[`@financial-times/x-gift-article renders a default With native share on > <button className="o-buttons o-buttons--primary o-buttons--big" + disabled={true} onClick={[Function]} type="button" > @@ -436,6 +541,7 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits > <button className="o-buttons o-buttons--primary o-buttons--big" + disabled={true} onClick={[Function]} type="button" > From 59deda9d828238e0f53aae62b08fcaf4c0b75da3 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Tue, 4 Dec 2018 14:10:27 +0000 Subject: [PATCH 172/760] reset error state --- components/x-gift-article/src/GiftArticle.jsx | 2 +- components/x-gift-article/src/lib/updaters.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 3dba859bb..86142a2de 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -114,7 +114,7 @@ const withGiftFormActions = withActions( if (giftCredits > 0 || giftCredits === 0) { return updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); } else { - return { invalidResponseFromApi: true } + return { invalidResponseFromApi: true }; } } } diff --git a/components/x-gift-article/src/lib/updaters.js b/components/x-gift-article/src/lib/updaters.js index c91e4365a..f50d1113b 100644 --- a/components/x-gift-article/src/lib/updaters.js +++ b/components/x-gift-article/src/lib/updaters.js @@ -47,7 +47,8 @@ export const setAllowance = (giftCredits, monthlyAllowance, nextRenewalDate) => return { giftCredits, monthlyAllowance, - nextRenewalDateText + nextRenewalDateText, + invalidResponseFromApi: false }; }; From d456cea422211145509630dfd2231953efea4793 Mon Sep 17 00:00:00 2001 From: Matt Brennan <matt@153.io> Date: Tue, 4 Dec 2018 14:38:38 +0000 Subject: [PATCH 173/760] mmm --- components/x-gift-article/src/lib/api.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 74ad573d3..0b5594660 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -28,12 +28,12 @@ export default class ApiClient { async getGiftArticleAllowance() { try { - const { credits } = await this.fetchJson('/article/gift-credits'); + const json = await this.fetchJson('/article/gift-credits'); return { - monthlyAllowance: credits.allowance, - giftCredits: credits.remainingCredits, - nextRenewalDate: credits.renewalDate + monthlyAllowance: json.allowance, + giftCredits: json.remainingCredits, + nextRenewalDate: json.renewalDate }; } catch (e) { return { monthlyAllowance: undefined, giftCredits: undefined, nextRenewalDate: undefined }; From 520aef3bd613b938110fe38ededddbafaef0eaa5 Mon Sep 17 00:00:00 2001 From: "Tye, Thurston" <Thurston.Tye@ft.com> Date: Thu, 16 May 2019 23:17:30 +0100 Subject: [PATCH 174/760] reinstate space just for premium label --- components/x-teaser/src/Title.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/src/Title.jsx b/components/x-teaser/src/Title.jsx index 17825cd6b..a56880c86 100644 --- a/components/x-teaser/src/Title.jsx +++ b/components/x-teaser/src/Title.jsx @@ -16,8 +16,11 @@ export default ({ title, altTitle, headlineTesting, relativeUrl, url, indicators {displayTitle} </Link> {indicators && indicators.accessLevel === 'premium' ? ( - <span className={premiumClass} aria-label="Premium content"> - Premium + <span> + {' '} + <span className={premiumClass} aria-label="Premium content"> + Premium + </span> </span> ) : null} </div> From 7470fd78703a20d631992d8e033d681f6f429760 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Fri, 17 May 2019 13:01:08 +0100 Subject: [PATCH 175/760] Configure Gatsby to Ignore Storybook and package source files --- tools/x-docs/gatsby-config.js | 5 ++++- tools/x-docs/src/lib/create-npm-package-pages.js | 5 ++++- tools/x-docs/src/lib/decorate-nodes.js | 9 ++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tools/x-docs/gatsby-config.js b/tools/x-docs/gatsby-config.js index cb35d20d3..fd2ed24c7 100644 --- a/tools/x-docs/gatsby-config.js +++ b/tools/x-docs/gatsby-config.js @@ -26,7 +26,10 @@ module.exports = { resolve: 'gatsby-source-filesystem', options: { name: 'components', - path: '../../components' + path: '../../components', + // Don't attempt to load any Storybook or source files, as these may + // contain syntax and/or features we cannot parse. + ignore: [/stories/, /src/] }, }, { diff --git a/tools/x-docs/src/lib/create-npm-package-pages.js b/tools/x-docs/src/lib/create-npm-package-pages.js index a8e5cba60..983c3f92b 100644 --- a/tools/x-docs/src/lib/create-npm-package-pages.js +++ b/tools/x-docs/src/lib/create-npm-package-pages.js @@ -1,3 +1,4 @@ +const fs = require('fs'); const path = require('path'); module.exports = async (actions, graphql) => { @@ -9,6 +10,7 @@ module.exports = async (actions, graphql) => { name manifest fields { + dir slug source } @@ -37,8 +39,9 @@ module.exports = async (actions, graphql) => { source: node.fields.source, packageName: node.manifest.name, packageDescription: node.manifest.description, + // Flag if Storybook demos are available for this package + storybook: fs.existsSync(path.join(node.fields.dir, 'stories', 'index.js')), // Associate readme and story nodes via slug - storiesPath: path.join(pagePath, 'stories'), packagePath: path.join(pagePath, 'package'), readmePath: path.join(pagePath, 'readme') } diff --git a/tools/x-docs/src/lib/decorate-nodes.js b/tools/x-docs/src/lib/decorate-nodes.js index bb9ab50d1..1b162c1f5 100644 --- a/tools/x-docs/src/lib/decorate-nodes.js +++ b/tools/x-docs/src/lib/decorate-nodes.js @@ -1,6 +1,6 @@ const path = require('path'); -const nodeTypesToSlug = new Set(['MarkdownRemark', 'NpmPackage', 'Stories']); +const nodeTypesToSlug = new Set(['MarkdownRemark', 'NpmPackage']); const repoRoot = path.resolve('../../'); @@ -17,12 +17,19 @@ module.exports = (node, actions, getNode) => { const file = getNode(node.parent); // Group files by source type (currently: docs, components, packages) + // "Source" meaning the name of the filesystem plugin instance actions.createNodeField({ node, name: 'source', value: file.sourceInstanceName }); + actions.createNodeField({ + node, + name: 'dir', + value: file.dir + }); + actions.createNodeField({ node, name: 'slug', From 17c698d4fee6642513bdddcc687b247d82055df0 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Fri, 17 May 2019 13:02:54 +0100 Subject: [PATCH 176/760] Remove story selector tabs from package story viewer component --- .../src/components/story-viewer/index.jsx | 58 +++++-------------- tools/x-docs/static/main.css | 29 ---------- 2 files changed, 14 insertions(+), 73 deletions(-) diff --git a/tools/x-docs/src/components/story-viewer/index.jsx b/tools/x-docs/src/components/story-viewer/index.jsx index 109e1380b..ab8044b92 100644 --- a/tools/x-docs/src/components/story-viewer/index.jsx +++ b/tools/x-docs/src/components/story-viewer/index.jsx @@ -1,52 +1,22 @@ import React from 'react'; import { withPrefix } from 'gatsby'; -class StoryViewer extends React.Component { - constructor(props) { - super(props); +const StoryViewer = ({ name }) => { + const queryString = `?path=/story/${name}--*`; + const iframeUrl = withPrefix(`/storybook/iframe.html${queryString}`); + const linkUrl = withPrefix(`/storybook/index.html${queryString}`); - this.state = { - selected: 0 - }; - } - - onClick(index) { - if (this.state.selected !== index) { - this.setState({ selected: index }); - } - } - - render() { - const story = this.props.stories[this.state.selected]; - const queryString = `?selectedKind=${this.props.name}&selectedStory=${story}`; - const iframeUrl = withPrefix(`/storybook/iframe.html${queryString}`); - const linkUrl = withPrefix(`/storybook/index.html${queryString}`); - - return ( - <div id="component-demos" className="story-viewer"> - <h2 className="story-viewer__heading">Component demos</h2> - <ul className="story-viewer__list" role="tablist"> - {this.props.stories.map((story, i) => ( - <li key={`story-${i}`} className="story-viewer__item"> - <button - role="tab" - className="story-viewer__button" - aria-selected={this.state.selected === i} - onClick={this.onClick.bind(this, i)}> - {story} - </button> - </li> - ))} - </ul> - <div className="story-viewer__panel" role="tabpanel"> - <iframe title={`${story} demo`} src={iframeUrl}></iframe> - </div> - <p className="story-viewer__footer"> - <a href={linkUrl}>View in Storybook</a> - </p> + return ( + <div id="component-demos" className="story-viewer"> + <h2 className="story-viewer__heading">Component demos</h2> + <div className="story-viewer__panel"> + <iframe title={`${name} demo`} src={iframeUrl}></iframe> </div> - ); - } + <p className="story-viewer__footer"> + <a href={linkUrl}>View in Storybook</a> + </p> + </div> + ) } export default StoryViewer; diff --git a/tools/x-docs/static/main.css b/tools/x-docs/static/main.css index cd809c1e4..485faa001 100644 --- a/tools/x-docs/static/main.css +++ b/tools/x-docs/static/main.css @@ -681,35 +681,6 @@ td:first-child { padding-left: 0; } -.story-viewer__item { - margin-left: 0.5rem; -} - -.story-viewer__item:first-child { - margin-left: 0; -} - -.story-viewer__button { - padding: 0.25rem 0.5rem; - font: inherit; - font-size: 0.95rem; - border: 1px solid; - color: var(--o-colors-oxford); - background: none; - cursor: pointer; -} - -.story-viewer__button:hover, -.story-viewer__button:focus { - color: var(--o-colors-oxford-100); -} - -.story-viewer__button[aria-selected=true] { - border: 1px solid transparent; - color: var(--o-colors-white); - background: var(--o-colors-oxford); -} - .story-viewer__panel { height: 20rem; resize: vertical; From 45c5ddb2789fb2582a288d620c7b3cadacf0f458 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Fri, 17 May 2019 13:16:15 +0100 Subject: [PATCH 177/760] Update package page links to link to the first available component story --- tools/x-docs/src/components/tertiary/links.jsx | 2 +- tools/x-docs/src/templates/npm-package.jsx | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/tools/x-docs/src/components/tertiary/links.jsx b/tools/x-docs/src/components/tertiary/links.jsx index f1ed0c28b..f7f2c6891 100644 --- a/tools/x-docs/src/components/tertiary/links.jsx +++ b/tools/x-docs/src/components/tertiary/links.jsx @@ -22,7 +22,7 @@ export default ({ name, manifest, storybook }) => ( </li> {storybook ? ( <li className="tertiary-menu__item"> - <Link to={`/storybook/index.html?selectedKind=${name}`} {...linkProps}> + <Link to={`/storybook/index.html?path=/story/${name}--*`} {...linkProps}> Storybook </Link> </li> diff --git a/tools/x-docs/src/templates/npm-package.jsx b/tools/x-docs/src/templates/npm-package.jsx index 0be87644a..a4d8b701f 100644 --- a/tools/x-docs/src/templates/npm-package.jsx +++ b/tools/x-docs/src/templates/npm-package.jsx @@ -20,9 +20,7 @@ export default ({ pageContext, data, location }) => ( <main className="content-layout__main" role="main"> <div className="content-layout__main-inner"> <div className="markdown" dangerouslySetInnerHTML={{ __html: data.markdown.html }} /> - {data.storybook ? ( - <StoryViewer name={pageContext.title} stories={data.storybook.stories} /> - ) : null} + {pageContext.storybook ? <StoryViewer name={pageContext.title} /> : null} </div> </main> <div className="content-layout__tertiary"> @@ -30,9 +28,9 @@ export default ({ pageContext, data, location }) => ( <Links name={pageContext.title} manifest={data.npm.manifest} - storybook={Boolean(data.storybook)} + storybook={pageContext.storybook} /> - <Subheadings items={data.markdown.headings} demos={Boolean(data.storybook)} /> + <Subheadings items={data.markdown.headings} demos={pageContext.storybook} /> </div> </div> </div> @@ -40,7 +38,7 @@ export default ({ pageContext, data, location }) => ( ); export const pageQuery = graphql` - query($type: String!, $packagePath: String!, $readmePath: String!, $storiesPath: String!) { + query($type: String!, $packagePath: String!, $readmePath: String!) { npm: npmPackage(fields: { slug: { eq: $packagePath } }) { manifest } @@ -51,9 +49,6 @@ export const pageQuery = graphql` depth } } - storybook: stories(fields: { slug: { eq: $storiesPath } }) { - stories - } modules: allSitePage(filter: { context: { type: { eq: $type } } }) { edges { node { From 93c645bd556d0c6ddf19324d8111b75873f2300e Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Fri, 17 May 2019 13:34:29 +0100 Subject: [PATCH 178/760] Refactor npm package page generator to use default file node information --- tools/x-docs/src/lib/create-npm-package-pages.js | 5 +++-- tools/x-docs/src/lib/decorate-nodes.js | 6 ------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/tools/x-docs/src/lib/create-npm-package-pages.js b/tools/x-docs/src/lib/create-npm-package-pages.js index 983c3f92b..cd331da56 100644 --- a/tools/x-docs/src/lib/create-npm-package-pages.js +++ b/tools/x-docs/src/lib/create-npm-package-pages.js @@ -9,8 +9,8 @@ module.exports = async (actions, graphql) => { node { name manifest + fileAbsolutePath fields { - dir slug source } @@ -27,6 +27,7 @@ module.exports = async (actions, graphql) => { result.data.npmPackages.edges.map(({ node }) => { // Package manifest slug will be /package so remove it const pagePath = path.dirname(node.fields.slug); + const relPath = path.dirname(node.fileAbsolutePath); actions.createPage({ component: path.resolve('src/templates/npm-package.jsx'), @@ -40,7 +41,7 @@ module.exports = async (actions, graphql) => { packageName: node.manifest.name, packageDescription: node.manifest.description, // Flag if Storybook demos are available for this package - storybook: fs.existsSync(path.join(node.fields.dir, 'stories', 'index.js')), + storybook: fs.existsSync(path.join(relPath, 'stories', 'index.js')), // Associate readme and story nodes via slug packagePath: path.join(pagePath, 'package'), readmePath: path.join(pagePath, 'readme') diff --git a/tools/x-docs/src/lib/decorate-nodes.js b/tools/x-docs/src/lib/decorate-nodes.js index 1b162c1f5..6de6662c3 100644 --- a/tools/x-docs/src/lib/decorate-nodes.js +++ b/tools/x-docs/src/lib/decorate-nodes.js @@ -24,12 +24,6 @@ module.exports = (node, actions, getNode) => { value: file.sourceInstanceName }); - actions.createNodeField({ - node, - name: 'dir', - value: file.dir - }); - actions.createNodeField({ node, name: 'slug', From c32154f6571b75f7b022315520b90a62cdfe199f Mon Sep 17 00:00:00 2001 From: Thurston Tye <thurston.tye@ft.com> Date: Mon, 20 May 2019 15:49:05 +0100 Subject: [PATCH 179/760] bring changelog uptodate (#306) --- changelog.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/changelog.md b/changelog.md index 29a8b9726..b052d757e 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,19 @@ ## v1 +### v1.0.0-beta.12 + +- bring changlog upto date (#306) +- reinstate space just for premium label (#304) + +### v1.0.0-beta.11 + +- x-gift article (#78) + +### v1.0.0-beta.10 + +- Remove extra space after title text(#282) + ### v1.0.0-beta.9 - Refactors x-teaser to make the teaser standfirst a link (#268) From 4d433fa16104ff01546d3b9678d02a355edfc52e Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Fri, 31 May 2019 11:56:08 +0100 Subject: [PATCH 180/760] Allow more than one digit after tag metadata for prerelease tags (#317) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 59ca3a95e..b609a8461 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -140,7 +140,7 @@ jobs: name: Extract tag name and version number command: | # https://circleci.com/docs/2.0/env-vars/#using-bash_env-to-set-environment-variables - export PRERELEASE_SEMVER='v0\.[0-9]{1,2}\.[0-9]{1,2}(-[a-z]+\.[0-9])?' + export PRERELEASE_SEMVER='v0\.[0-9]{1,2}\.[0-9]{1,2}(-[a-z]+\.[0-9]+)?' export TARGET_VERSION=$(echo $CIRCLE_TAG | grep -o -E $PRERELEASE_SEMVER); export TARGET_MODULE=$(echo $CIRCLE_TAG | sed -E "s/-${PRERELEASE_SEMVER}//g"); echo "export TARGET_VERSION=$TARGET_VERSION" >> $BASH_ENV; From 6da0d293ba9d3c9fc913a14cf8dcfc2088fa53ac Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Fri, 31 May 2019 17:40:40 +0100 Subject: [PATCH 181/760] Override Storybook Babel configuration for x-components source files with x-dash Babel configuration --- .storybook/webpack.config.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 702d61362..0f8b49005 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -3,6 +3,7 @@ const path = require('path'); const fs = require('fs'); +const xBabelConfig = require('../packages/x-babel-config'); const xEngine = require('../packages/x-engine/src/webpack'); const CopyPlugin = require('copy-webpack-plugin'); const WritePlugin = require('write-file-webpack-plugin'); @@ -35,11 +36,20 @@ module.exports = ({ config }) => { const jsRule = config.module.rules.find((rule) => rule.test.test('.jsx')); jsRule.exclude = excludePaths; + // HACK: Instruct Babel to check module type before injecting Core JS polyfills // https://github.com/i-like-robots/broken-webpack-bundle-test-case const babelConfig = jsRule.use.find(({ loader }) => loader === 'babel-loader'); babelConfig.options.sourceType = 'unambiguous'; + // Override the Babel configuration for all x- components with our own + babelConfig.options.overrides = [ + { + test: /\/components\/x-[^\/]+\/src\//, + ...xBabelConfig() + } + ]; + // HACK: Ensure we only bundle one instance of React config.resolve.alias.react = require.resolve('react'); From d99a1195697f6599dc8519c8d7b9c30e22d9299f Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Mon, 3 Jun 2019 09:03:05 +0100 Subject: [PATCH 182/760] Abstract teaser component story data out of Storybook configuration into separate JSON files --- components/x-teaser/__fixtures__/article.json | 29 ++++++++++++ .../__fixtures__/content-package.json | 26 ++++++++++ components/x-teaser/__fixtures__/opinion.json | 31 ++++++++++++ .../x-teaser/__fixtures__/package-item.json | 23 +++++++++ components/x-teaser/__fixtures__/podcast.json | 24 ++++++++++ .../x-teaser/__fixtures__/promoted.json | 14 ++++++ .../x-teaser/__fixtures__/top-story.json | 46 ++++++++++++++++++ components/x-teaser/__fixtures__/video.json | 31 ++++++++++++ components/x-teaser/stories/article.js | 32 +------------ .../{package.js => content-package.js} | 31 ++---------- components/x-teaser/stories/index.js | 6 +-- components/x-teaser/stories/opinion.js | 36 ++------------ components/x-teaser/stories/package-item.js | 29 ++---------- components/x-teaser/stories/podcast.js | 27 +---------- components/x-teaser/stories/promoted.js | 15 +----- components/x-teaser/stories/top-story.js | 47 +------------------ components/x-teaser/stories/video.js | 36 +------------- 17 files changed, 248 insertions(+), 235 deletions(-) create mode 100644 components/x-teaser/__fixtures__/article.json create mode 100644 components/x-teaser/__fixtures__/content-package.json create mode 100644 components/x-teaser/__fixtures__/opinion.json create mode 100644 components/x-teaser/__fixtures__/package-item.json create mode 100644 components/x-teaser/__fixtures__/podcast.json create mode 100644 components/x-teaser/__fixtures__/promoted.json create mode 100644 components/x-teaser/__fixtures__/top-story.json create mode 100644 components/x-teaser/__fixtures__/video.json rename components/x-teaser/stories/{package.js => content-package.js} (57%) diff --git a/components/x-teaser/__fixtures__/article.json b/components/x-teaser/__fixtures__/article.json new file mode 100644 index 000000000..22486d022 --- /dev/null +++ b/components/x-teaser/__fixtures__/article.json @@ -0,0 +1,29 @@ +{ + "type": "article", + "id": "", + "url": "#", + "title": "Inside charity fundraiser where hostesses are put on show", + "altTitle": "Men Only, the charity fundraiser with hostesses on show", + "standfirst": "FT investigation finds groping and sexual harassment at secretive black-tie dinner", + "altStandfirst": "Groping and sexual harassment at black-tie dinner charity event", + "publishedDate": "2018-01-23T15:07:00.000Z", + "firstPublishedDate": "2018-01-23T13:53:00.000Z", + "metaPrefixText": "", + "metaSuffixText": "", + "metaLink": { + "url": "#", + "prefLabel": "Sexual misconduct allegations" + }, + "metaAltLink": { + "url": "#", + "prefLabel": "FT Investigations" + }, + "image": { + "url": "http://prod-upp-image-read.ft.com/a25832ea-0053-11e8-9650-9c0ad2d7c5b5", + "width": 2048, + "height": 1152 + }, + "indicators": { + "isEditorsChoice": true + } +} diff --git a/components/x-teaser/__fixtures__/content-package.json b/components/x-teaser/__fixtures__/content-package.json new file mode 100644 index 000000000..fa85fafe5 --- /dev/null +++ b/components/x-teaser/__fixtures__/content-package.json @@ -0,0 +1,26 @@ +{ + "type": "package", + "id": "", + "url": "#", + "title": "The royal wedding", + "altTitle": "", + "standfirst": "Prince Harry and Meghan Markle will tie the knot at Windsor Castle", + "altStandfirst": "", + "publishedDate": "2018-05-14T16:38:49.000Z", + "firstPublishedDate": "2018-05-14T16:38:49.000Z", + "metaPrefixText": "", + "metaSuffixText": "", + "metaLink": { + "url": "#", + "prefLabel": "FT Magazine" + }, + "metaAltLink": { + "url": "#", + "prefLabel": "FT Series" + }, + "image": { + "url": "http://prod-upp-image-read.ft.com/7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0", + "width": 2048, + "height": 1152 + } +} diff --git a/components/x-teaser/__fixtures__/opinion.json b/components/x-teaser/__fixtures__/opinion.json new file mode 100644 index 000000000..d455871bd --- /dev/null +++ b/components/x-teaser/__fixtures__/opinion.json @@ -0,0 +1,31 @@ +{ + "type": "article", + "id": "", + "url": "#", + "title": "Anti-Semitism and the threat of identity politics", + "altTitle": "", + "standfirst": "Today, hatred of Jews is mixed in with fights about Islam and Israel", + "altStandfirst": "Anti-Semitism and identity politics", + "publishedDate": "2018-04-02T12:22:01.000Z", + "firstPublishedDate": "2018-04-02T12:22:01.000Z", + "metaPrefixText": "", + "metaSuffixText": "", + "metaLink": { + "url": "#", + "prefLabel": "Gideon Rachman" + }, + "metaAltLink": { + "url": "#", + "prefLabel": "Anti-Semitism" + }, + "image": { + "url": "http://prod-upp-image-read.ft.com/1005ca96-364b-11e8-8b98-2f31af407cc8", + "width": 2048, + "height": 1152 + }, + "headshot": "fthead-v1:gideon-rachman", + "indicators": { + "isOpinion": true, + "isColumn": true + } +} diff --git a/components/x-teaser/__fixtures__/package-item.json b/components/x-teaser/__fixtures__/package-item.json new file mode 100644 index 000000000..cdb8a9985 --- /dev/null +++ b/components/x-teaser/__fixtures__/package-item.json @@ -0,0 +1,23 @@ +{ + "type": "article", + "id": "", + "url": "#", + "title": "Why so little has changed since the crash", + "standfirst": "Martin Wolf on the power of vested interests in today’s rent-extracting economy", + "publishedDate": "2018-09-02T15:07:00.000Z", + "firstPublishedDate": "2018-09-02T13:53:00.000Z", + "metaPrefixText": "FT Series", + "metaSuffixText": "", + "metaLink": { + "url": "#", + "prefLabel": "Financial crisis: Are we safer now? " + }, + "image": { + "url": "http://prod-upp-image-read.ft.com/a25832ea-0053-11e8-9650-9c0ad2d7c5b5", + "width": 2048, + "height": 1152 + }, + "indicators": { + "isOpinion": true + } +} diff --git a/components/x-teaser/__fixtures__/podcast.json b/components/x-teaser/__fixtures__/podcast.json new file mode 100644 index 000000000..fcb7b98f2 --- /dev/null +++ b/components/x-teaser/__fixtures__/podcast.json @@ -0,0 +1,24 @@ +{ + "type": "audio", + "id": "d1246074-f7d3-4aaf-951c-80a6db495765", + "url": "https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765", + "title": "Who sets the internet standards?", + "standfirst": "Hannah Kuchler talks to American social scientist and cyber security expert Andrea…", + "altStandfirst": "", + "publishedDate": "2018-10-24T04:00:00.000Z", + "firstPublishedDate": "2018-10-24T04:00:00.000Z", + "metaSuffixText": "12 mins", + "metaLink": { + "url": "#", + "prefLabel": "Tech Tonic podcast" + }, + "metaAltLink": null, + "image": { + "url": "https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d?source=next&fit=scale-down&compression=best&width=240", + "width": 2048, + "height": 1152 + }, + "indicators": { + "isPodcast": true + } +} diff --git a/components/x-teaser/__fixtures__/promoted.json b/components/x-teaser/__fixtures__/promoted.json new file mode 100644 index 000000000..aa95056a9 --- /dev/null +++ b/components/x-teaser/__fixtures__/promoted.json @@ -0,0 +1,14 @@ +{ + "type": "paid-post", + "id": "", + "url": "#", + "title": "Why eSports companies are on a winning streak", + "standfirst": "ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020", + "promotedPrefixText": "Paid post", + "promotedSuffixText": "by UBS", + "image": { + "url": "https://tpc.googlesyndication.com/pagead/imgad?id=CICAgKCrm_3yahABGAEyCMx3RoLss603", + "width": 700, + "height": 394 + } +} diff --git a/components/x-teaser/__fixtures__/top-story.json b/components/x-teaser/__fixtures__/top-story.json new file mode 100644 index 000000000..9b117bb93 --- /dev/null +++ b/components/x-teaser/__fixtures__/top-story.json @@ -0,0 +1,46 @@ +{ + "type": "article", + "id": "", + "url": "#", + "title": "Inside charity fundraiser where hostesses are put on show", + "altTitle": "Men Only, the charity fundraiser with hostesses on show", + "standfirst": "FT investigation finds groping and sexual harassment at secretive black-tie dinner", + "altStandfirst": "Groping and sexual harassment at black-tie dinner charity event", + "publishedDate": "2018-01-23T15:07:00.000Z", + "firstPublishedDate": "2018-01-23T13:53:00.000Z", + "metaPrefixText": "", + "metaSuffixText": "", + "metaLink": { + "url": "#", + "prefLabel": "Sexual misconduct allegations" + }, + "metaAltLink": { + "url": "#", + "prefLabel": "FT Investigations" + }, + "image": { + "url": "http://prod-upp-image-read.ft.com/a25832ea-0053-11e8-9650-9c0ad2d7c5b5", + "width": 2048, + "height": 1152 + }, + "relatedLinks": [ + { + "id": "", + "url": "#", + "type": "article", + "title": "Removing the fig leaf of charity" + }, + { + "id": "", + "url": "#", + "type": "article", + "title": "A dinner that demeaned both women and men" + }, + { + "id": "", + "url": "#", + "type": "video", + "title": "PM speaks out after Presidents Club dinner" + } + ] +} diff --git a/components/x-teaser/__fixtures__/video.json b/components/x-teaser/__fixtures__/video.json new file mode 100644 index 000000000..5db90c9d9 --- /dev/null +++ b/components/x-teaser/__fixtures__/video.json @@ -0,0 +1,31 @@ +{ + "type": "video", + "id": "0e89d872-5711-457b-80b1-4ca0d8afea46", + "url": "#", + "title": "FT View: Donald Trump, man of steel", + "standfirst": "The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs", + "publishedDate": "2018-03-26T08:12:28.137Z", + "firstPublishedDate": "2018-03-26T08:12:28.137Z", + "metaPrefixText": "", + "metaSuffixText": "02:51min", + "metaLink": { + "url": "#", + "prefLabel": "Global Trade" + }, + "metaAltLink": { + "url": "#", + "prefLabel": "US" + }, + "image": { + "url": "http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194", + "width": 1920, + "height": 1080 + }, + "video": { + "url": "https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4", + "width": 640, + "height": 360, + "mediaType": "video/mp4", + "codec": "h264" + } +} diff --git a/components/x-teaser/stories/article.js b/components/x-teaser/stories/article.js index 7da5fea98..79c5862db 100644 --- a/components/x-teaser/stories/article.js +++ b/components/x-teaser/stories/article.js @@ -4,35 +4,7 @@ exports.title = 'Article'; // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign({ - type: 'article', - id: '', - url: '#', - title: 'Inside charity fundraiser where hostesses are put on show', - altTitle: 'Men Only, the charity fundraiser with hostesses on show', - standfirst: 'FT investigation finds groping and sexual harassment at secretive black-tie dinner', - altStandfirst: 'Groping and sexual harassment at black-tie dinner charity event', - publishedDate: '2018-01-23T15:07:00.000Z', - firstPublishedDate: '2018-01-23T13:53:00.000Z', - metaPrefixText: '', - metaSuffixText: '', - metaLink: { - url: '#', - prefLabel: 'Sexual misconduct allegations' - }, - metaAltLink: { - url: '#', - prefLabel: 'FT Investigations' - }, - image: { - url: 'http://prod-upp-image-read.ft.com/a25832ea-0053-11e8-9650-9c0ad2d7c5b5', - width: 2048, - height: 1152 - }, - indicators: { - isEditorsChoice: true - } -}, presets.SmallHeavy); +exports.data = Object.assign(require('../__fixtures__/article.json'), presets.SmallHeavy); // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -69,7 +41,7 @@ exports.knobs = [ 'headlineTesting', // Variants 'layout', - 'modifiers', + 'modifiers' ]; // This reference is only required for hot module loading in development diff --git a/components/x-teaser/stories/package.js b/components/x-teaser/stories/content-package.js similarity index 57% rename from components/x-teaser/stories/package.js rename to components/x-teaser/stories/content-package.js index 05a3e1bbc..b96d5c6bc 100644 --- a/components/x-teaser/stories/package.js +++ b/components/x-teaser/stories/content-package.js @@ -4,32 +4,9 @@ exports.title = 'Content Package'; // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign({ - type: 'package', - id: '', - url: '#', - title: 'The royal wedding', - altTitle: '', - standfirst: 'Prince Harry and Meghan Markle will tie the knot at Windsor Castle', - altStandfirst: '', - publishedDate: '2018-05-14T16:38:49.000Z', - firstPublishedDate: '2018-05-14T16:38:49.000Z', - metaPrefixText: '', - metaSuffixText: '', - metaLink: { - url: '#', - prefLabel: 'FT Magazine' - }, - metaAltLink: { - url: '#', - prefLabel: 'FT Series' - }, - image: { - url: 'http://prod-upp-image-read.ft.com/7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0', - width: 2048, - height: 1152 - } -}, presets.Hero, { modifiers: 'centre' }); +exports.data = Object.assign(require('../__fixtures__/content-package.json'), presets.Hero, { + modifiers: 'centre' +}); // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -58,7 +35,7 @@ exports.knobs = [ // Variants 'layout', 'theme', - 'modifiers', + 'modifiers' ]; // This reference is only required for hot module loading in development diff --git a/components/x-teaser/stories/index.js b/components/x-teaser/stories/index.js index 63b6fac6d..216c17193 100644 --- a/components/x-teaser/stories/index.js +++ b/components/x-teaser/stories/index.js @@ -10,18 +10,18 @@ exports.dependencies = { 'o-typography': '^5.5.0', 'o-teaser': '^3.5.0', 'o-labels': '^4.2.1', - 'o-video': '^4.1.0', + 'o-video': '^4.1.0' }; exports.stories = [ require('./article'), require('./podcast'), require('./opinion'), - require('./package'), + require('./content-package'), require('./package-item'), require('./promoted'), require('./top-story'), - require('./video'), + require('./video') ]; exports.knobs = require('./knobs'); diff --git a/components/x-teaser/stories/opinion.js b/components/x-teaser/stories/opinion.js index bd6be8047..235bcf739 100644 --- a/components/x-teaser/stories/opinion.js +++ b/components/x-teaser/stories/opinion.js @@ -4,37 +4,9 @@ exports.title = 'Opinion Piece'; // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign({ - type: 'article', - id: '', - url: '#', - title: 'Anti-Semitism and the threat of identity politics', - altTitle: '', - standfirst: 'Today, hatred of Jews is mixed in with fights about Islam and Israel', - altStandfirst: 'Anti-Semitism and identity politics', - publishedDate: '2018-04-02T12:22:01.000Z', - firstPublishedDate: '2018-04-02T12:22:01.000Z', - metaPrefixText: '', - metaSuffixText: '', - metaLink: { - url: '#', - prefLabel: 'Gideon Rachman' - }, - metaAltLink: { - url: '#', - prefLabel: 'Anti-Semitism' - }, - image: { - url: 'http://prod-upp-image-read.ft.com/1005ca96-364b-11e8-8b98-2f31af407cc8', - width: 2048, - height: 1152 - }, - headshot: 'fthead-v1:gideon-rachman', - indicators: { - isOpinion: true, - isColumn: true - } -}, presets.SmallHeavy, { showHeadshot: true }); +exports.data = Object.assign(require('../__fixtures__/opinion.json'), presets.SmallHeavy, { + showHeadshot: true +}); // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -70,7 +42,7 @@ exports.knobs = [ 'layout', 'modifiers', // Indicators - 'indicators', + 'indicators' ]; // This reference is only required for hot module loading in development diff --git a/components/x-teaser/stories/package-item.js b/components/x-teaser/stories/package-item.js index 5de22f7a6..17dde23e3 100644 --- a/components/x-teaser/stories/package-item.js +++ b/components/x-teaser/stories/package-item.js @@ -4,29 +4,10 @@ exports.title = 'Package item'; // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign({ - type: 'article', - id: '', - url: '#', - title: 'Why so little has changed since the crash', - standfirst: 'Martin Wolf on the power of vested interests in today’s rent-extracting economy', - publishedDate: '2018-09-02T15:07:00.000Z', - firstPublishedDate: '2018-09-02T13:53:00.000Z', - metaPrefixText: 'FT Series', - metaSuffixText: '', - metaLink: { - url: '#', - prefLabel: 'Financial crisis: Are we safer now? ' - }, - image: { - url: 'http://prod-upp-image-read.ft.com/a25832ea-0053-11e8-9650-9c0ad2d7c5b5', - width: 2048, - height: 1152 - }, - indicators: { - isOpinion: true - } -}, presets.Hero, { parentTheme: 'extra-article', modifiers: 'centre' }); +exports.data = Object.assign(require('../__fixtures__/package-item.json'), presets.Hero, { + parentTheme: 'extra-article', + modifiers: 'centre' +}); // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -60,7 +41,7 @@ exports.knobs = [ 'layout', 'theme', 'parentTheme', - 'modifiers', + 'modifiers' ]; // This reference is only required for hot module loading in development diff --git a/components/x-teaser/stories/podcast.js b/components/x-teaser/stories/podcast.js index 4527ba98e..65b494525 100644 --- a/components/x-teaser/stories/podcast.js +++ b/components/x-teaser/stories/podcast.js @@ -4,30 +4,7 @@ exports.title = 'Podcast'; // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign({ - type: 'audio', - id: 'd1246074-f7d3-4aaf-951c-80a6db495765', - url: 'https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765', - title: 'Who sets the internet standards?', - standfirst: 'Hannah Kuchler talks to American social scientist and cyber security expert Andrea…', - altStandfirst: '', - publishedDate: '2018-10-24T04:00:00.000Z', - firstPublishedDate: '2018-10-24T04:00:00.000Z', - metaSuffixText: '12 mins', - metaLink: { - url: '#', - prefLabel: 'Tech Tonic podcast' - }, - metaAltLink: null, - image: { - url: 'https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d?source=next&fit=scale-down&compression=best&width=240', - width: 2048, - height: 1152 - }, - indicators: { - "isPodcast": true - } -}, presets.SmallHeavy); +exports.data = Object.assign(require('../__fixtures__/podcast.json'), presets.SmallHeavy); // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -64,7 +41,7 @@ exports.knobs = [ 'headlineTesting', // Variants 'layout', - 'modifiers', + 'modifiers' ]; // This reference is only required for hot module loading in development diff --git a/components/x-teaser/stories/promoted.js b/components/x-teaser/stories/promoted.js index 3ec41dcd5..1d7d22ed2 100644 --- a/components/x-teaser/stories/promoted.js +++ b/components/x-teaser/stories/promoted.js @@ -4,20 +4,7 @@ exports.title = 'Paid Post'; // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign({ - type: 'paid-post', - id: '', - url: '#', - title: 'Why eSports companies are on a winning streak', - standfirst: 'ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020', - promotedPrefixText: 'Paid post', - promotedSuffixText: 'by UBS', - image: { - url: 'https://tpc.googlesyndication.com/pagead/imgad?id=CICAgKCrm_3yahABGAEyCMx3RoLss603', - width: 700, - height: 394 - } -}, presets.SmallHeavy); +exports.data = Object.assign(require('../__fixtures__/promoted.json'), presets.SmallHeavy); // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. diff --git a/components/x-teaser/stories/top-story.js b/components/x-teaser/stories/top-story.js index ffc299040..ef9bc6cb2 100644 --- a/components/x-teaser/stories/top-story.js +++ b/components/x-teaser/stories/top-story.js @@ -4,52 +4,7 @@ exports.title = 'Top Story'; // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign({ - type: 'article', - id: '', - url: '#', - title: 'Inside charity fundraiser where hostesses are put on show', - altTitle: 'Men Only, the charity fundraiser with hostesses on show', - standfirst: 'FT investigation finds groping and sexual harassment at secretive black-tie dinner', - altStandfirst: 'Groping and sexual harassment at black-tie dinner charity event', - publishedDate: '2018-01-23T15:07:00.000Z', - firstPublishedDate: '2018-01-23T13:53:00.000Z', - metaPrefixText: '', - metaSuffixText: '', - metaLink: { - url: '#', - prefLabel: 'Sexual misconduct allegations' - }, - metaAltLink: { - url: '#', - prefLabel: 'FT Investigations' - }, - image: { - url: 'http://prod-upp-image-read.ft.com/a25832ea-0053-11e8-9650-9c0ad2d7c5b5', - width: 2048, - height: 1152 - }, - relatedLinks: [ - { - id: '', - url: '#', - type: 'article', - title: 'Removing the fig leaf of charity' - }, - { - id: '', - url: '#', - type: 'article', - title: 'A dinner that demeaned both women and men' - }, - { - id: '', - url: '#', - type: 'video', - title: 'PM speaks out after Presidents Club dinner' - } - ] -}, presets.TopStoryLandscape); +exports.data = Object.assign(require('../__fixtures__/top-story.json'), presets.TopStoryLandscape); // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. diff --git a/components/x-teaser/stories/video.js b/components/x-teaser/stories/video.js index 28059f0ba..bdb6db8fd 100644 --- a/components/x-teaser/stories/video.js +++ b/components/x-teaser/stories/video.js @@ -4,39 +4,7 @@ exports.title = 'Video'; // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign({ - type: 'video', - // The ID is required for the in-situ video demo to work - // NOTE: o-video is not be called on component mount so won't render anyway. - id: '0e89d872-5711-457b-80b1-4ca0d8afea46', - url: '#', - title: 'FT View: Donald Trump, man of steel', - standfirst: 'The FT\'s Rob Armstrong looks at why Donald Trump is pushing trade tariffs', - publishedDate: '2018-03-26T08:12:28.137Z', - firstPublishedDate: '2018-03-26T08:12:28.137Z', - metaPrefixText: '', - metaSuffixText: '02:51min', - metaLink: { - url: '#', - prefLabel: 'Global Trade' - }, - metaAltLink: { - url: '#', - prefLabel: 'US' - }, - image: { - url: 'http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194', - width: 1920, - height: 1080 - }, - video: { - url: 'https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4', - width: 640, - height: 360, - mediaType: 'video/mp4', - codec: 'h264' - } -}, presets.HeroVideo); +exports.data = Object.assign(require('../__fixtures__/video.json'), presets.HeroVideo); // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -66,7 +34,7 @@ exports.knobs = [ 'video', // Variants 'layout', - 'modifiers', + 'modifiers' ]; // This reference is only required for hot module loading in development From 53a627ea8fc5e2023f3608214050bca8fafa420e Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Mon, 3 Jun 2019 09:14:14 +0100 Subject: [PATCH 183/760] Add separate teaser component snapshot tests --- .../__snapshots__/snapshots.test.js.snap | 4505 +++++++++++++++++ .../x-teaser/__tests__/snapshots.test.js | 27 + 2 files changed, 4532 insertions(+) create mode 100644 components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap create mode 100644 components/x-teaser/__tests__/snapshots.test.js diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap new file mode 100644 index 000000000..353ffe0af --- /dev/null +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -0,0 +1,4505 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--hero o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-image js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroNarrow teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--hero js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroNarrow teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroNarrow teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--hero js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tabIndex={-1} + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroNarrow teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--hero js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroNarrow teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroNarrow teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--hero js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroVideo teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--hero js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroVideo teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroVideo teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--hero js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroVideo teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--hero js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroVideo teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__video" + > + <div + className="o--if-js" + > + <div + className="o-teaser__image-container js-image-container" + > + <div + className="o-video" + data-o-component="o-video" + data-o-video-autorender="true" + data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" + data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-placeholder="true" + data-o-video-placeholder-hint="Play video" + data-o-video-placeholder-info="[]" + data-o-video-playsinline="true" + /> + </div> + </div> + <div + className="o--if-no-js" + > + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--large o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--large o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tabIndex={-1} + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--large o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--large o-teaser--has-image js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Small teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--small js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Small teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Small teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--small js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Small teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--small js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Small teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a Small teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--small js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--small o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--small o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tabIndex={-1} + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--small o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--small o-teaser--has-image js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStory teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--top-story js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStory teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStory teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--top-story js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tabIndex={-1} + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStory teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--top-story js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStory teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStory teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--top-story js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs + </a> + </p> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPackage data 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageItem data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast data 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tabIndex={-1} + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted data 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with video data 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; diff --git a/components/x-teaser/__tests__/snapshots.test.js b/components/x-teaser/__tests__/snapshots.test.js new file mode 100644 index 000000000..ca85dd9ba --- /dev/null +++ b/components/x-teaser/__tests__/snapshots.test.js @@ -0,0 +1,27 @@ +const { h } = require('@financial-times/x-engine'); +const renderer = require('react-test-renderer'); +const { Teaser, presets } = require('../'); + +const storyData = { + article: require('../__fixtures__/article.json'), + opinion: require('../__fixtures__/opinion.json'), + contentPackage: require('../__fixtures__/content-package.json'), + packageItem: require('../__fixtures__/package-item.json'), + podcast: require('../__fixtures__/podcast.json'), + video: require('../__fixtures__/video.json'), + promoted: require('../__fixtures__/promoted.json'), + topStory: require('../__fixtures__/top-story.json') +}; + +describe('x-teaser / snapshots', () => { + Object.entries(storyData).forEach(([type, data]) => { + Object.entries(presets).forEach(([name, settings]) => { + it(`renders a ${name} teaser with ${type} data`, () => { + const props = { ...data, ...settings }; + const tree = renderer.create(h(Teaser, props)).toJSON(); + + expect(tree).toMatchSnapshot(); + }); + }); + }); +}); From 6ed75541b9f7b6be9e890c860bfc8d586ddd51f9 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Mon, 3 Jun 2019 09:17:34 +0100 Subject: [PATCH 184/760] Rename teaser component storybook configuration to skip automatic snapshot creation --- .storybook/register-components.js | 2 +- .../__snapshots__/snapshots.test.js.snap | 5841 ----------------- .../{stories => storybook}/article.js | 0 .../{stories => storybook}/content-package.js | 0 .../x-teaser/{stories => storybook}/index.js | 0 .../x-teaser/{stories => storybook}/knobs.js | 0 .../{stories => storybook}/opinion.js | 0 .../{stories => storybook}/package-item.js | 0 .../{stories => storybook}/podcast.js | 0 .../{stories => storybook}/promoted.js | 0 .../{stories => storybook}/top-story.js | 0 .../x-teaser/{stories => storybook}/video.js | 0 12 files changed, 1 insertion(+), 5842 deletions(-) rename components/x-teaser/{stories => storybook}/article.js (100%) rename components/x-teaser/{stories => storybook}/content-package.js (100%) rename components/x-teaser/{stories => storybook}/index.js (100%) rename components/x-teaser/{stories => storybook}/knobs.js (100%) rename components/x-teaser/{stories => storybook}/opinion.js (100%) rename components/x-teaser/{stories => storybook}/package-item.js (100%) rename components/x-teaser/{stories => storybook}/podcast.js (100%) rename components/x-teaser/{stories => storybook}/promoted.js (100%) rename components/x-teaser/{stories => storybook}/top-story.js (100%) rename components/x-teaser/{stories => storybook}/video.js (100%) diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 6fad0c357..7ea941718 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -1,5 +1,5 @@ const components = [ - require('../components/x-teaser/stories'), + require('../components/x-teaser/storybook'), require('../components/x-increment/stories'), require('../components/x-styling-demo/stories'), require('../components/x-gift-article/stories'), diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index b549f95da..116317544 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -589,5844 +589,3 @@ exports[`@financial-times/x-styling-demo renders a default Styling x-styling-dem Click me! </button> `; - -exports[`@financial-times/x-teaser renders a Hero Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs - </a> - </p> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--hero o-teaser--hero-image o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--large o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--large o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--large o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--large o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--large o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--large o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--large o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs - </a> - </p> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--small o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--small o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--small o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--small o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--small o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--small o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--small o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--small o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs - </a> - </p> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--top-story o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--top-story o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--top-story o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--top-story o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs - </a> - </p> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStoryLandscape Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStoryLandscape Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStoryLandscape Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </a> - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStoryLandscape Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStoryLandscape Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStoryLandscape Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tabIndex={-1} - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fwww.ft.com%2F__origami%2Fservice%2Fimage%2Fv2%2Fimages%2Fraw%2Fhttp%253A%252F%252Fprod-upp-image-read.ft.com%252F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d%3Fsource%3Dnext%26fit%3Dscale-down%26compression%3Dbest%26width%3D240?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStoryLandscape Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStoryLandscape Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--top-story o-teaser--landscape o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="#" - tabIndex={-1} - > - The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs - </a> - </p> - </div> -</div> -`; diff --git a/components/x-teaser/stories/article.js b/components/x-teaser/storybook/article.js similarity index 100% rename from components/x-teaser/stories/article.js rename to components/x-teaser/storybook/article.js diff --git a/components/x-teaser/stories/content-package.js b/components/x-teaser/storybook/content-package.js similarity index 100% rename from components/x-teaser/stories/content-package.js rename to components/x-teaser/storybook/content-package.js diff --git a/components/x-teaser/stories/index.js b/components/x-teaser/storybook/index.js similarity index 100% rename from components/x-teaser/stories/index.js rename to components/x-teaser/storybook/index.js diff --git a/components/x-teaser/stories/knobs.js b/components/x-teaser/storybook/knobs.js similarity index 100% rename from components/x-teaser/stories/knobs.js rename to components/x-teaser/storybook/knobs.js diff --git a/components/x-teaser/stories/opinion.js b/components/x-teaser/storybook/opinion.js similarity index 100% rename from components/x-teaser/stories/opinion.js rename to components/x-teaser/storybook/opinion.js diff --git a/components/x-teaser/stories/package-item.js b/components/x-teaser/storybook/package-item.js similarity index 100% rename from components/x-teaser/stories/package-item.js rename to components/x-teaser/storybook/package-item.js diff --git a/components/x-teaser/stories/podcast.js b/components/x-teaser/storybook/podcast.js similarity index 100% rename from components/x-teaser/stories/podcast.js rename to components/x-teaser/storybook/podcast.js diff --git a/components/x-teaser/stories/promoted.js b/components/x-teaser/storybook/promoted.js similarity index 100% rename from components/x-teaser/stories/promoted.js rename to components/x-teaser/storybook/promoted.js diff --git a/components/x-teaser/stories/top-story.js b/components/x-teaser/storybook/top-story.js similarity index 100% rename from components/x-teaser/stories/top-story.js rename to components/x-teaser/storybook/top-story.js diff --git a/components/x-teaser/stories/video.js b/components/x-teaser/storybook/video.js similarity index 100% rename from components/x-teaser/stories/video.js rename to components/x-teaser/storybook/video.js From 049e4a14bf3e1daa3b55d47d4ee45269798383aa Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Mon, 3 Jun 2019 17:51:38 +0100 Subject: [PATCH 185/760] Update docs site to look for story configuration in either stories or storybook directories --- .../plugins/gatsby-transformer-npm-package/on-create-node.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/x-docs/plugins/gatsby-transformer-npm-package/on-create-node.js b/tools/x-docs/plugins/gatsby-transformer-npm-package/on-create-node.js index 682ece288..9370a734b 100644 --- a/tools/x-docs/plugins/gatsby-transformer-npm-package/on-create-node.js +++ b/tools/x-docs/plugins/gatsby-transformer-npm-package/on-create-node.js @@ -30,7 +30,9 @@ module.exports = ({ node, actions }) => { createParentChildLink({ parent: node, child: npmPackageNode }); } - if (node.internal.type === 'File' && node.absolutePath.endsWith('stories/index.js')) { + const storybookFiles = ['stories/index.js', 'storybook/index.js'] + + if (node.internal.type === 'File' && storybookFiles.some((filename) => node.absolutePath.endsWith(filename))) { const contents = require(node.absolutePath); const storiesNode = { From 0fe50a1ea3afc3d7e7ce5713d1faaa5ecefbcb3a Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Fri, 7 Jun 2019 11:53:45 +0100 Subject: [PATCH 186/760] Import fetch-mock package from source to avoid core-js version mismatch errors Currently fetch-mock ships a transpiled copy of its code for use in the browser. This requires several Babel runtime and Core JS helpers and polyfills. However the code is transpiled using an older version of Babel which uses core-js v2 but everything we now use depends on core-js v3. https://github.com/wheresrhys/fetch-mock/issues/419 --- .storybook/build-story.js | 6 +++++- .storybook/webpack.config.js | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.storybook/build-story.js b/.storybook/build-story.js index 1c7c49bba..03802dc7e 100644 --- a/.storybook/build-story.js +++ b/.storybook/build-story.js @@ -4,7 +4,11 @@ import { storiesOf } from '@storybook/react'; import * as knobsAddon from '@storybook/addon-knobs'; import { Helmet } from 'react-helmet'; import path from 'path'; -import fetchMock from 'fetch-mock'; + +// HACK: The browser bundle for Fetch Mock implicitly depends on core-js 2.x. +// We no longer use core-js 2.x anywhere so importing the browser bundle will fail. +// <https://github.com/wheresrhys/fetch-mock/issues/419> +import fetchMock from 'fetch-mock/src/client.js'; const defaultKnobs = () => ({}); diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 0f8b49005..12734e134 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -8,7 +8,8 @@ const xEngine = require('../packages/x-engine/src/webpack'); const CopyPlugin = require('copy-webpack-plugin'); const WritePlugin = require('write-file-webpack-plugin'); -const excludePaths = [/node_modules/, /dist/]; +// HACK: we need to include fetch-mock from source so it must be transpiled +const excludePaths = [/node_modules\/(?!(fetch-mock)\/)/, /dist/]; const cssCopy = fs.readdirSync(path.resolve('components')).reduce((mains, component) => { const componentPkg = path.resolve('components', component, 'package.json'); From ce67de82a9b85b82f6abc53c2bdadaa99d1e7c93 Mon Sep 17 00:00:00 2001 From: Dawn Budge <dawn.budge@ft.com> Date: Wed, 5 Jun 2019 16:50:47 +0100 Subject: [PATCH 187/760] Convenience make command for building just one component --- docs/integration/local-development.md | 10 ++++++++++ makefile | 3 +++ package.json | 1 + 3 files changed, 14 insertions(+) diff --git a/docs/integration/local-development.md b/docs/integration/local-development.md index 1771d7263..2f69d5e7a 100644 --- a/docs/integration/local-development.md +++ b/docs/integration/local-development.md @@ -46,3 +46,13 @@ If you encounter this error, ensure that x-dash is set up correctly in the paren ## Avoid linking Usually, using a locally-installed version of a package is a use case for `npm link`. In practice, we have found it to be brittle, causing problems with peer dependencies and nested transitive dependencies. Using relative `npm install` treats the installed package as any other, ensuring that your `node_modules` directory has the expected structure. + +## Build one component only + +If you don't want to build all the components while you are working on one, there is a convenience `make` command that allows you to limit builds by keyword + +Use your package's name with the `build-` command on the command line + +```bash +make build-x-name-of-component +``` \ No newline at end of file diff --git a/makefile b/makefile index 80adfff97..7788f5293 100644 --- a/makefile +++ b/makefile @@ -13,6 +13,9 @@ install: build: npm run build +build-%: + npm run build-only -- --filter $* + test: npm run test diff --git a/package.json b/package.json index 99fdea400..3bc57d1b1 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "scripts": { "clean": "git clean -fxdi", "build": "athloi run build --concurrency 3 && npm run build-storybook", + "build-only": "athloi run build", "jest": "jest -c jest.config.js", "pretest": "npm run build", "test": "npm run lint && npm run jest", From 78eb0baf9153f689d0d84ab8a8f4f2d2c447d10f Mon Sep 17 00:00:00 2001 From: Asuka Ochi <asuka.ochi@ft.com> Date: Mon, 10 Jun 2019 13:03:30 +0100 Subject: [PATCH 188/760] Fix tabable image issue This is a part of fixing DAC issues. Teasers from x-teaser provides tabable images link which leads repetitive navigation for the screen reader users. --- components/x-teaser/src/Image.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 193e22229..932abbc64 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -36,7 +36,7 @@ export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) <div className="o-teaser__image-placeholder" style={{ paddingBottom: aspectRatio(image) }}> <Link {...props} url={displayUrl} attrs={{ 'data-trackable': 'image-link', - 'tab-index': '-1', + 'tabIndex': '-1', 'aria-hidden': 'true', }}> <ImageComponent src={imageSrc} lazyLoad={imageLazyLoad} /> From b466ceb47813910c0e5d6f80c3c86951964c5beb Mon Sep 17 00:00:00 2001 From: Asuka Ochi <asuka.ochi@ft.com> Date: Mon, 10 Jun 2019 14:57:32 +0100 Subject: [PATCH 189/760] Update snapshots --- .../__snapshots__/snapshots.test.js.snap | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 353ffe0af..6bb9f1ac5 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -50,7 +50,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -113,7 +113,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -176,7 +176,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -244,7 +244,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] = aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -312,7 +312,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = ` aria-hidden="true" data-trackable="image-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -378,7 +378,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -441,7 +441,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -509,7 +509,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -990,7 +990,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`] aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1053,7 +1053,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1116,7 +1116,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1184,7 +1184,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1252,7 +1252,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`] aria-hidden="true" data-trackable="image-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1318,7 +1318,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1381,7 +1381,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1449,7 +1449,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] = aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1809,7 +1809,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1898,7 +1898,7 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1973,7 +1973,7 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2048,7 +2048,7 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2128,7 +2128,7 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] = aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2208,7 +2208,7 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = ` aria-hidden="true" data-trackable="image-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2286,7 +2286,7 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2361,7 +2361,7 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2441,7 +2441,7 @@ exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = ` aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2838,7 +2838,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`] aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2913,7 +2913,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2988,7 +2988,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3068,7 +3068,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3148,7 +3148,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`] aria-hidden="true" data-trackable="image-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3226,7 +3226,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`] aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3301,7 +3301,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`] aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3381,7 +3381,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] = aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3911,7 +3911,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3986,7 +3986,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4061,7 +4061,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4141,7 +4141,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4221,7 +4221,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da aria-hidden="true" data-trackable="image-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4299,7 +4299,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4374,7 +4374,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4491,7 +4491,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with video data aria-hidden="true" data-trackable="image-link" href="#" - tab-index="-1" + tabIndex="-1" > <img alt="" From 4b444e614d52dc9d0ed7151c40e1e837d65de117 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Mon, 10 Jun 2019 15:52:14 +0100 Subject: [PATCH 190/760] Bump Storybook from 5.0.x to v5.1.x --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 99fdea400..9be2044c4 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "devDependencies": { "@babel/core": "^7.1.5", "@financial-times/athloi": "^1.0.0-beta.19", - "@storybook/addon-knobs": "^5.0.10", - "@storybook/addon-viewport": "^5.0.10", - "@storybook/react": "^5.0.10", + "@storybook/addon-knobs": "^5.1.2", + "@storybook/addon-viewport": "^5.1.2", + "@storybook/react": "^5.1.2", "acorn": "^6.0.2", "acorn-jsx": "^5.0.0", "babel-loader": "^8.0.4", From 7d768a5619c837abbaa25506fa2dcf169b279d52 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Mon, 10 Jun 2019 16:22:06 +0100 Subject: [PATCH 191/760] Add comments about old configuration based story definitions --- .storybook/config.js | 4 ++++ .storybook/register-components.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/.storybook/config.js b/.storybook/config.js index d593fe2bf..8b4d4b628 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -3,6 +3,10 @@ import buildStory from './build-story'; import * as components from './register-components'; configure(() => { + // We used to use a configuration based technique for defining stories for each component as we + // wanted to reuse the data in multiple places. We now recommend you define stories in a regular + // way as defined by the Storybook documentation: + // <https://storybook.js.org/docs/basics/writing-stories/> components.forEach(({ stories, ...data }) => { stories.forEach((story) => buildStory({ story, ...data })); }); diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 7ea941718..ee8e8cf6c 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -1,3 +1,5 @@ +// NOTE: Only add configuration based component stories to this file. If you are using regular +// story definitions (i.e. implementing storiesOf() directly) then please add these to config.js const components = [ require('../components/x-teaser/storybook'), require('../components/x-increment/stories'), From e0f97164352a99e2f961157344165cf4542d3d02 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Mon, 10 Jun 2019 16:23:53 +0100 Subject: [PATCH 192/760] Refactor x-increment storybook demos to use regular story configuration --- .storybook/config.js | 3 +++ .storybook/register-components.js | 1 - components/x-increment/stories/async.js | 15 -------------- components/x-increment/stories/increment.js | 10 ---------- components/x-increment/stories/index.js | 10 ---------- components/x-increment/stories/knobs.js | 7 ------- components/x-increment/storybook/index.jsx | 22 +++++++++++++++++++++ 7 files changed, 25 insertions(+), 43 deletions(-) delete mode 100644 components/x-increment/stories/async.js delete mode 100644 components/x-increment/stories/increment.js delete mode 100644 components/x-increment/stories/index.js delete mode 100644 components/x-increment/stories/knobs.js create mode 100644 components/x-increment/storybook/index.jsx diff --git a/.storybook/config.js b/.storybook/config.js index 8b4d4b628..f9260ac8d 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -10,4 +10,7 @@ configure(() => { components.forEach(({ stories, ...data }) => { stories.forEach((story) => buildStory({ story, ...data })); }); + + // Add regular story definitions (i.e. those using storiesOf() directly below) + require('../components/x-increment/storybook/index.jsx'); }, module); diff --git a/.storybook/register-components.js b/.storybook/register-components.js index ee8e8cf6c..3421fb438 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -2,7 +2,6 @@ // story definitions (i.e. implementing storiesOf() directly) then please add these to config.js const components = [ require('../components/x-teaser/storybook'), - require('../components/x-increment/stories'), require('../components/x-styling-demo/stories'), require('../components/x-gift-article/stories'), ]; diff --git a/components/x-increment/stories/async.js b/components/x-increment/stories/async.js deleted file mode 100644 index 67764e1d2..000000000 --- a/components/x-increment/stories/async.js +++ /dev/null @@ -1,15 +0,0 @@ -exports.title = 'Async'; - -const data = { - count: 1, - timeout: 1000, - id: 'base-increment-static-id', -}; - -exports.data = data; - -exports.knobs = Object.keys(data); - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; diff --git a/components/x-increment/stories/increment.js b/components/x-increment/stories/increment.js deleted file mode 100644 index 904bdd73c..000000000 --- a/components/x-increment/stories/increment.js +++ /dev/null @@ -1,10 +0,0 @@ -exports.title = 'Increment'; - -exports.data = { - count: 1, - id: 'base-increment-static-id', -}; - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; diff --git a/components/x-increment/stories/index.js b/components/x-increment/stories/index.js deleted file mode 100644 index 211967353..000000000 --- a/components/x-increment/stories/index.js +++ /dev/null @@ -1,10 +0,0 @@ -const { Increment } = require('../'); - -exports.component = Increment; -exports.package = require('../package.json'); -exports.stories = [ - require('./increment'), - require('./async'), -]; - -exports.knobs = require('./knobs'); diff --git a/components/x-increment/stories/knobs.js b/components/x-increment/stories/knobs.js deleted file mode 100644 index 7bf3de5bd..000000000 --- a/components/x-increment/stories/knobs.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = (data, { number }) => { - return { - count() { - return number('Count', data.count, {}); - } - }; -}; diff --git a/components/x-increment/storybook/index.jsx b/components/x-increment/storybook/index.jsx new file mode 100644 index 000000000..ae1fd3455 --- /dev/null +++ b/components/x-increment/storybook/index.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { Increment } from '../src/Increment'; + +storiesOf('x-increment', module) + .add('Sync', () => { + const data = { + count: 1, + id: 'base-increment-static-id' + }; + + return <Increment {...data} />; + }) + .add('Async', () => { + const data = { + count: 1, + timeout: 1000, + id: 'base-increment-static-id' + }; + + return <Increment {...data} />; + }); From 6e99b3ed4d8d201c09edbff58baef70551460d2f Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Mon, 10 Jun 2019 16:44:21 +0100 Subject: [PATCH 193/760] update eslint config to more strictly target component source files --- .eslintrc.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c34913c6b..1f7c10720 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,9 +4,7 @@ module.exports = { browser: true, es6: true }, - plugins: [ - 'jsx-a11y' - ], + plugins: ['jsx-a11y'], extends: [ 'eslint:recommended', // https://github.com/jest-community/eslint-plugin-jest @@ -38,12 +36,12 @@ module.exports = { 'react/no-unescaped-entities': 'off', // this rule is deprecated and replaced with label-has-associated-control 'jsx-a11y/label-has-for': 'off', - 'jsx-a11y/label-has-associated-control': 'error', + 'jsx-a11y/label-has-associated-control': 'error' }, overrides: [ { // Components in x-dash interact with x-engine rather than React - files: [ 'components/**/*.jsx' ], + files: ['components/*/src/**/*.jsx', 'components/*/__tests__/**/*.jsx'], settings: { react: { pragma: 'h', From b5dac0ae86fab0a5e8c6e919e2b6cdf3f071a4e1 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Mon, 10 Jun 2019 17:15:13 +0100 Subject: [PATCH 194/760] Remove increment component from global snapshot data --- .../__snapshots__/snapshots.test.js.snap | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 116317544..19d78391b 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -554,34 +554,6 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits </div> `; -exports[`@financial-times/x-increment renders a default Async x-increment 1`] = ` -<div> - <span> - 1 - </span> - <button - disabled={false} - onClick={[Function]} - > - Increment - </button> -</div> -`; - -exports[`@financial-times/x-increment renders a default Increment x-increment 1`] = ` -<div> - <span> - 1 - </span> - <button - disabled={false} - onClick={[Function]} - > - Increment - </button> -</div> -`; - exports[`@financial-times/x-styling-demo renders a default Styling x-styling-demo 1`] = ` <button className="Button_button__vS7Mv" From d4679cce4002205abdd65b6c6ece4b414de0b580 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Mon, 10 Jun 2019 18:55:04 +0100 Subject: [PATCH 195/760] Update changelog.md --- changelog.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index b052d757e..9da905199 100644 --- a/changelog.md +++ b/changelog.md @@ -2,18 +2,21 @@ ## v1 +### v1.0.0-beta.13 + +- Fixes incorrect `tab-index` attribute definition in x-teaser (#335) + ### v1.0.0-beta.12 -- bring changlog upto date (#306) -- reinstate space just for premium label (#304) +- Refactors x-teaser to add a space between the title and premium label (#304) ### v1.0.0-beta.11 -- x-gift article (#78) +- Added x-gift article (#78) ### v1.0.0-beta.10 -- Remove extra space after title text(#282) +- Refactors x-teaser to remove an extra space after the title text (#282) ### v1.0.0-beta.9 From a51e3237db37e4888b2a5fcde318876c23b9f8ec Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 09:06:14 +0100 Subject: [PATCH 196/760] Update all dev dependencies, remove unnecessary extra resolutions --- package.json | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 9be2044c4..5c265745f 100644 --- a/package.json +++ b/package.json @@ -14,27 +14,24 @@ "heroku-postbuild": "make install && npm run build" }, "devDependencies": { - "@babel/core": "^7.1.5", - "@financial-times/athloi": "^1.0.0-beta.19", - "@storybook/addon-knobs": "^5.1.2", - "@storybook/addon-viewport": "^5.1.2", - "@storybook/react": "^5.1.2", - "acorn": "^6.0.2", - "acorn-jsx": "^5.0.0", + "@babel/core": "^7.4.5", + "@financial-times/athloi": "^1.0.0-beta.26", + "@storybook/addon-knobs": "^5.1.8", + "@storybook/addon-viewport": "^5.1.8", + "@storybook/react": "^5.1.8", "babel-loader": "^8.0.4", - "copy-webpack-plugin": "^4.6.0", - "eslint": "^5.1.0", - "eslint-config-prettier": "^2.9.0", - "eslint-plugin-jest": "^21.17.0", - "eslint-plugin-jsx-a11y": "^6.1.1", - "eslint-plugin-react": "^7.10.0", - "espree": "^4.1.0", - "fetch-mock": "^6.5.2", - "jest": "^24.7.1", - "node-sass": "^4.11.0", - "react": "^16.3.1", + "copy-webpack-plugin": "^5.0.2", + "eslint": "^5.16.0", + "eslint-config-prettier": "^5.0.0", + "eslint-plugin-jest": "^22.6.4", + "eslint-plugin-jsx-a11y": "^6.2.0", + "eslint-plugin-react": "^7.13.0", + "fetch-mock": "^7.3.3", + "jest": "^24.8.0", + "node-sass": "^4.12.0", + "react": "^16.8.6", "react-helmet": "^5.2.0", - "react-test-renderer": "^16.3.1", + "react-test-renderer": "^16.8.6", "sass-loader": "^7.1.0", "style-loader": "^0.23.1", "write-file-webpack-plugin": "^4.5.0" From 72989ef5b99679076058041c67b89b508de4ecb9 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 09:18:56 +0100 Subject: [PATCH 197/760] Add core-js v2 as an explicit dependency and undo changes made in #331 --- .storybook/build-story.js | 7 +++---- .storybook/webpack.config.js | 3 +-- package.json | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.storybook/build-story.js b/.storybook/build-story.js index 03802dc7e..a7ccfe2da 100644 --- a/.storybook/build-story.js +++ b/.storybook/build-story.js @@ -5,10 +5,9 @@ import * as knobsAddon from '@storybook/addon-knobs'; import { Helmet } from 'react-helmet'; import path from 'path'; -// HACK: The browser bundle for Fetch Mock implicitly depends on core-js 2.x. -// We no longer use core-js 2.x anywhere so importing the browser bundle will fail. -// <https://github.com/wheresrhys/fetch-mock/issues/419> -import fetchMock from 'fetch-mock/src/client.js'; +// HACK: The browser bundle for Fetch Mock implicitly depends on core-js 2.x so ensure +// that this is an explicit dependency of the repository root. +import fetchMock from 'fetch-mock'; const defaultKnobs = () => ({}); diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 12734e134..0f8b49005 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -8,8 +8,7 @@ const xEngine = require('../packages/x-engine/src/webpack'); const CopyPlugin = require('copy-webpack-plugin'); const WritePlugin = require('write-file-webpack-plugin'); -// HACK: we need to include fetch-mock from source so it must be transpiled -const excludePaths = [/node_modules\/(?!(fetch-mock)\/)/, /dist/]; +const excludePaths = [/node_modules/, /dist/]; const cssCopy = fs.readdirSync(path.resolve('components')).reduce((mains, component) => { const componentPkg = path.resolve('components', component, 'package.json'); diff --git a/package.json b/package.json index 5c265745f..44d785227 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@storybook/react": "^5.1.8", "babel-loader": "^8.0.4", "copy-webpack-plugin": "^5.0.2", + "core-js": "^2.6.8", "eslint": "^5.16.0", "eslint-config-prettier": "^5.0.0", "eslint-plugin-jest": "^22.6.4", From d05508c6c27fac6369627b1def77b7619978c0df Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 09:37:06 +0100 Subject: [PATCH 198/760] Fix jest linting issues --- __tests__/snapshots.test.js | 2 +- components/x-interaction/__tests__/x-interaction.test.jsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/__tests__/snapshots.test.js b/__tests__/snapshots.test.js index 31d12f76b..e2c685598 100644 --- a/__tests__/snapshots.test.js +++ b/__tests__/snapshots.test.js @@ -21,7 +21,7 @@ for(const pkg of packageDirs) { const { presets = { default: {} } } = require(pkgDir); const name = path.basename(pkg.name); - describe(pkg.name, () => { + describe(`${pkg.name}`, () => { for (const { title, data } of stories) { for (const [preset, options] of Object.entries(presets)) { it(`renders a ${preset} ${title} ${name}`, () => { diff --git a/components/x-interaction/__tests__/x-interaction.test.jsx b/components/x-interaction/__tests__/x-interaction.test.jsx index 6fd01be10..16b842bba 100644 --- a/components/x-interaction/__tests__/x-interaction.test.jsx +++ b/components/x-interaction/__tests__/x-interaction.test.jsx @@ -218,7 +218,7 @@ describe('x-interaction', () => { target.find(Base).prop('bar') ).toBe(15); }); - + it('should pass changed outside props to state updaters', async () => { const Base = () => null; const Wrapped = withActions({ @@ -311,6 +311,4 @@ describe('x-interaction', () => { }); }); - - describe.skip('server rendering'); }); From 6bcd9776728d323b6cdf90cb691fdeeaf8863d20 Mon Sep 17 00:00:00 2001 From: Samuel Parkinson <samuel.parkinson@ft.com> Date: Wed, 26 Jun 2019 10:54:26 +0100 Subject: [PATCH 199/760] feat: specify optimum sizes for video teasers This will limit the size of the streamed videos to a 360p resolution, rather than the default of 1080p. The lower resolution will be better for anyone on a wobbly connection. --- components/x-teaser/src/Video.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/x-teaser/src/Video.jsx b/components/x-teaser/src/Video.jsx index 383dc22d5..eb68e8c50 100644 --- a/components/x-teaser/src/Video.jsx +++ b/components/x-teaser/src/Video.jsx @@ -16,6 +16,8 @@ const Embed = (props) => ( data-o-component="o-video" data-o-video-id={props.id} data-o-video-data={formatData(props)} + data-o-video-optimumwidth="640" + data-o-video-optimumvideowidth="640" data-o-video-autorender="true" data-o-video-playsinline="true" data-o-video-placeholder="true" From dfa9b9f0211890d5626441db7a842372fe3a811c Mon Sep 17 00:00:00 2001 From: Samuel Parkinson <51677+sjparkinson@users.noreply.github.com> Date: Wed, 26 Jun 2019 11:40:23 +0100 Subject: [PATCH 200/760] tests: update the x-teaser Jest snapshot --- .../x-teaser/__tests__/__snapshots__/snapshots.test.js.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 6bb9f1ac5..99ebca3fb 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -1784,6 +1784,8 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` data-o-video-autorender="true" data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-optimumvideowidth="640" + data-o-video-optimumwidth="640" data-o-video-placeholder="true" data-o-video-placeholder-hint="Play video" data-o-video-placeholder-info="[]" From 75b3a569c2de3173532d08b18172ab04c9eca1f2 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Wed, 26 Jun 2019 12:10:49 +0100 Subject: [PATCH 201/760] Update changelog.md --- changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.md b/changelog.md index 9da905199..0a03508b3 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,10 @@ ## v1 +### v1.0.0-beta.14 + +- Adds missing optimum video size configuration to x-teaser (#351) + ### v1.0.0-beta.13 - Fixes incorrect `tab-index` attribute definition in x-teaser (#335) From dd432bcae0cbc841847d9a80895ae1df76c62ad8 Mon Sep 17 00:00:00 2001 From: "Jennifer.Shepherd" <jennifer.shepherd@ft.com> Date: Tue, 28 May 2019 17:17:10 +0100 Subject: [PATCH 202/760] Add Snyk to app So that our repos avoid exposure to security vunerabilities, Snyk will monitor them for us. --- .circleci/config.yml | 2 ++ .snyk | 4 ++++ package.json | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .snyk diff --git a/.circleci/config.yml b/.circleci/config.yml index b609a8461..7aecb0bcf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -122,6 +122,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} @@ -136,6 +137,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash - run: name: Extract tag name and version number command: | diff --git a/.snyk b/.snyk new file mode 100644 index 000000000..861428bd4 --- /dev/null +++ b/.snyk @@ -0,0 +1,4 @@ +# Snyk (https://snyk.io) policy file, which patches or ignores known vulnerabilities. +version: v1.13.5 +ignore: {} +patch: {} diff --git a/package.json b/package.json index 12c6b4467..972571734 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", "start-docs": "(cd tools/x-docs && npm start)", - "heroku-postbuild": "make install && npm run build" + "heroku-postbuild": "make install && npm run build", + "prepare": "npx snyk protect || npx snyk protect -d || true" }, "devDependencies": { "@babel/core": "^7.4.5", @@ -35,6 +36,7 @@ "react-helmet": "^5.2.0", "react-test-renderer": "^16.8.6", "sass-loader": "^7.1.0", + "snyk": "^1.168.0", "style-loader": "^0.23.1", "write-file-webpack-plugin": "^4.5.0" }, From 8bdd0531307abd9141f598de419e40a0712c74e9 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 10:52:54 +0100 Subject: [PATCH 203/760] Move x-docs from tools to top-level web folder --- {tools/x-docs => web}/.gitignore | 0 {tools/x-docs => web}/gatsby-config.js | 0 {tools/x-docs => web}/gatsby-node.js | 0 {tools/x-docs => web}/package.json | 0 .../extend-node-type.js | 0 .../gatsby-transformer-npm-package/gatsby-node.js | 0 .../on-create-node.js | 0 .../gatsby-transformer-npm-package/package.json | 0 .../x-docs => web}/src/components/footer/index.jsx | 0 .../x-docs => web}/src/components/header/index.jsx | 0 {tools/x-docs => web}/src/components/icon/index.jsx | 0 .../x-docs => web}/src/components/layouts/basic.jsx | 0 .../src/components/layouts/splash.jsx | 0 .../src/components/module-list/index.jsx | 0 .../src/components/sidebar/module-menu.jsx | 0 .../src/components/sidebar/pages-menu.jsx | 0 .../src/components/story-viewer/index.jsx | 0 .../src/components/tertiary/links.jsx | 0 .../src/components/tertiary/subheadings.jsx | 0 {tools/x-docs => web}/src/data/docs-menu.yml | 0 {tools/x-docs => web}/src/html.jsx | 0 .../src/lib/create-documentation-pages.js | 0 .../src/lib/create-npm-package-pages.js | 0 {tools/x-docs => web}/src/lib/decorate-nodes.js | 0 {tools/x-docs => web}/src/pages/components.jsx | 0 {tools/x-docs => web}/src/pages/index.jsx | 0 {tools/x-docs => web}/src/pages/packages.jsx | 0 .../src/templates/documentation-page.jsx | 0 {tools/x-docs => web}/src/templates/npm-package.jsx | 0 {tools/x-docs => web}/static/favicon.ico | Bin {tools/x-docs => web}/static/logo.png | Bin {tools/x-docs => web}/static/main.css | 0 {tools/x-docs => web}/static/prism.css | 0 {tools/x-docs => web}/static/storybook | 0 34 files changed, 0 insertions(+), 0 deletions(-) rename {tools/x-docs => web}/.gitignore (100%) rename {tools/x-docs => web}/gatsby-config.js (100%) rename {tools/x-docs => web}/gatsby-node.js (100%) rename {tools/x-docs => web}/package.json (100%) rename {tools/x-docs => web}/plugins/gatsby-transformer-npm-package/extend-node-type.js (100%) rename {tools/x-docs => web}/plugins/gatsby-transformer-npm-package/gatsby-node.js (100%) rename {tools/x-docs => web}/plugins/gatsby-transformer-npm-package/on-create-node.js (100%) rename {tools/x-docs => web}/plugins/gatsby-transformer-npm-package/package.json (100%) rename {tools/x-docs => web}/src/components/footer/index.jsx (100%) rename {tools/x-docs => web}/src/components/header/index.jsx (100%) rename {tools/x-docs => web}/src/components/icon/index.jsx (100%) rename {tools/x-docs => web}/src/components/layouts/basic.jsx (100%) rename {tools/x-docs => web}/src/components/layouts/splash.jsx (100%) rename {tools/x-docs => web}/src/components/module-list/index.jsx (100%) rename {tools/x-docs => web}/src/components/sidebar/module-menu.jsx (100%) rename {tools/x-docs => web}/src/components/sidebar/pages-menu.jsx (100%) rename {tools/x-docs => web}/src/components/story-viewer/index.jsx (100%) rename {tools/x-docs => web}/src/components/tertiary/links.jsx (100%) rename {tools/x-docs => web}/src/components/tertiary/subheadings.jsx (100%) rename {tools/x-docs => web}/src/data/docs-menu.yml (100%) rename {tools/x-docs => web}/src/html.jsx (100%) rename {tools/x-docs => web}/src/lib/create-documentation-pages.js (100%) rename {tools/x-docs => web}/src/lib/create-npm-package-pages.js (100%) rename {tools/x-docs => web}/src/lib/decorate-nodes.js (100%) rename {tools/x-docs => web}/src/pages/components.jsx (100%) rename {tools/x-docs => web}/src/pages/index.jsx (100%) rename {tools/x-docs => web}/src/pages/packages.jsx (100%) rename {tools/x-docs => web}/src/templates/documentation-page.jsx (100%) rename {tools/x-docs => web}/src/templates/npm-package.jsx (100%) rename {tools/x-docs => web}/static/favicon.ico (100%) rename {tools/x-docs => web}/static/logo.png (100%) rename {tools/x-docs => web}/static/main.css (100%) rename {tools/x-docs => web}/static/prism.css (100%) rename {tools/x-docs => web}/static/storybook (100%) diff --git a/tools/x-docs/.gitignore b/web/.gitignore similarity index 100% rename from tools/x-docs/.gitignore rename to web/.gitignore diff --git a/tools/x-docs/gatsby-config.js b/web/gatsby-config.js similarity index 100% rename from tools/x-docs/gatsby-config.js rename to web/gatsby-config.js diff --git a/tools/x-docs/gatsby-node.js b/web/gatsby-node.js similarity index 100% rename from tools/x-docs/gatsby-node.js rename to web/gatsby-node.js diff --git a/tools/x-docs/package.json b/web/package.json similarity index 100% rename from tools/x-docs/package.json rename to web/package.json diff --git a/tools/x-docs/plugins/gatsby-transformer-npm-package/extend-node-type.js b/web/plugins/gatsby-transformer-npm-package/extend-node-type.js similarity index 100% rename from tools/x-docs/plugins/gatsby-transformer-npm-package/extend-node-type.js rename to web/plugins/gatsby-transformer-npm-package/extend-node-type.js diff --git a/tools/x-docs/plugins/gatsby-transformer-npm-package/gatsby-node.js b/web/plugins/gatsby-transformer-npm-package/gatsby-node.js similarity index 100% rename from tools/x-docs/plugins/gatsby-transformer-npm-package/gatsby-node.js rename to web/plugins/gatsby-transformer-npm-package/gatsby-node.js diff --git a/tools/x-docs/plugins/gatsby-transformer-npm-package/on-create-node.js b/web/plugins/gatsby-transformer-npm-package/on-create-node.js similarity index 100% rename from tools/x-docs/plugins/gatsby-transformer-npm-package/on-create-node.js rename to web/plugins/gatsby-transformer-npm-package/on-create-node.js diff --git a/tools/x-docs/plugins/gatsby-transformer-npm-package/package.json b/web/plugins/gatsby-transformer-npm-package/package.json similarity index 100% rename from tools/x-docs/plugins/gatsby-transformer-npm-package/package.json rename to web/plugins/gatsby-transformer-npm-package/package.json diff --git a/tools/x-docs/src/components/footer/index.jsx b/web/src/components/footer/index.jsx similarity index 100% rename from tools/x-docs/src/components/footer/index.jsx rename to web/src/components/footer/index.jsx diff --git a/tools/x-docs/src/components/header/index.jsx b/web/src/components/header/index.jsx similarity index 100% rename from tools/x-docs/src/components/header/index.jsx rename to web/src/components/header/index.jsx diff --git a/tools/x-docs/src/components/icon/index.jsx b/web/src/components/icon/index.jsx similarity index 100% rename from tools/x-docs/src/components/icon/index.jsx rename to web/src/components/icon/index.jsx diff --git a/tools/x-docs/src/components/layouts/basic.jsx b/web/src/components/layouts/basic.jsx similarity index 100% rename from tools/x-docs/src/components/layouts/basic.jsx rename to web/src/components/layouts/basic.jsx diff --git a/tools/x-docs/src/components/layouts/splash.jsx b/web/src/components/layouts/splash.jsx similarity index 100% rename from tools/x-docs/src/components/layouts/splash.jsx rename to web/src/components/layouts/splash.jsx diff --git a/tools/x-docs/src/components/module-list/index.jsx b/web/src/components/module-list/index.jsx similarity index 100% rename from tools/x-docs/src/components/module-list/index.jsx rename to web/src/components/module-list/index.jsx diff --git a/tools/x-docs/src/components/sidebar/module-menu.jsx b/web/src/components/sidebar/module-menu.jsx similarity index 100% rename from tools/x-docs/src/components/sidebar/module-menu.jsx rename to web/src/components/sidebar/module-menu.jsx diff --git a/tools/x-docs/src/components/sidebar/pages-menu.jsx b/web/src/components/sidebar/pages-menu.jsx similarity index 100% rename from tools/x-docs/src/components/sidebar/pages-menu.jsx rename to web/src/components/sidebar/pages-menu.jsx diff --git a/tools/x-docs/src/components/story-viewer/index.jsx b/web/src/components/story-viewer/index.jsx similarity index 100% rename from tools/x-docs/src/components/story-viewer/index.jsx rename to web/src/components/story-viewer/index.jsx diff --git a/tools/x-docs/src/components/tertiary/links.jsx b/web/src/components/tertiary/links.jsx similarity index 100% rename from tools/x-docs/src/components/tertiary/links.jsx rename to web/src/components/tertiary/links.jsx diff --git a/tools/x-docs/src/components/tertiary/subheadings.jsx b/web/src/components/tertiary/subheadings.jsx similarity index 100% rename from tools/x-docs/src/components/tertiary/subheadings.jsx rename to web/src/components/tertiary/subheadings.jsx diff --git a/tools/x-docs/src/data/docs-menu.yml b/web/src/data/docs-menu.yml similarity index 100% rename from tools/x-docs/src/data/docs-menu.yml rename to web/src/data/docs-menu.yml diff --git a/tools/x-docs/src/html.jsx b/web/src/html.jsx similarity index 100% rename from tools/x-docs/src/html.jsx rename to web/src/html.jsx diff --git a/tools/x-docs/src/lib/create-documentation-pages.js b/web/src/lib/create-documentation-pages.js similarity index 100% rename from tools/x-docs/src/lib/create-documentation-pages.js rename to web/src/lib/create-documentation-pages.js diff --git a/tools/x-docs/src/lib/create-npm-package-pages.js b/web/src/lib/create-npm-package-pages.js similarity index 100% rename from tools/x-docs/src/lib/create-npm-package-pages.js rename to web/src/lib/create-npm-package-pages.js diff --git a/tools/x-docs/src/lib/decorate-nodes.js b/web/src/lib/decorate-nodes.js similarity index 100% rename from tools/x-docs/src/lib/decorate-nodes.js rename to web/src/lib/decorate-nodes.js diff --git a/tools/x-docs/src/pages/components.jsx b/web/src/pages/components.jsx similarity index 100% rename from tools/x-docs/src/pages/components.jsx rename to web/src/pages/components.jsx diff --git a/tools/x-docs/src/pages/index.jsx b/web/src/pages/index.jsx similarity index 100% rename from tools/x-docs/src/pages/index.jsx rename to web/src/pages/index.jsx diff --git a/tools/x-docs/src/pages/packages.jsx b/web/src/pages/packages.jsx similarity index 100% rename from tools/x-docs/src/pages/packages.jsx rename to web/src/pages/packages.jsx diff --git a/tools/x-docs/src/templates/documentation-page.jsx b/web/src/templates/documentation-page.jsx similarity index 100% rename from tools/x-docs/src/templates/documentation-page.jsx rename to web/src/templates/documentation-page.jsx diff --git a/tools/x-docs/src/templates/npm-package.jsx b/web/src/templates/npm-package.jsx similarity index 100% rename from tools/x-docs/src/templates/npm-package.jsx rename to web/src/templates/npm-package.jsx diff --git a/tools/x-docs/static/favicon.ico b/web/static/favicon.ico similarity index 100% rename from tools/x-docs/static/favicon.ico rename to web/static/favicon.ico diff --git a/tools/x-docs/static/logo.png b/web/static/logo.png similarity index 100% rename from tools/x-docs/static/logo.png rename to web/static/logo.png diff --git a/tools/x-docs/static/main.css b/web/static/main.css similarity index 100% rename from tools/x-docs/static/main.css rename to web/static/main.css diff --git a/tools/x-docs/static/prism.css b/web/static/prism.css similarity index 100% rename from tools/x-docs/static/prism.css rename to web/static/prism.css diff --git a/tools/x-docs/static/storybook b/web/static/storybook similarity index 100% rename from tools/x-docs/static/storybook rename to web/static/storybook From 3e33e89be583fe7c2e5f2135e5da586b353cebb0 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 10:54:22 +0100 Subject: [PATCH 204/760] Update Gatsby configuration to reflect updated paths --- web/gatsby-config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/gatsby-config.js b/web/gatsby-config.js index fd2ed24c7..c3c04848c 100644 --- a/web/gatsby-config.js +++ b/web/gatsby-config.js @@ -19,14 +19,14 @@ module.exports = { resolve: 'gatsby-source-filesystem', options: { name: 'docs', - path: '../../docs' + path: '../docs' }, }, { resolve: 'gatsby-source-filesystem', options: { name: 'components', - path: '../../components', + path: '../components', // Don't attempt to load any Storybook or source files, as these may // contain syntax and/or features we cannot parse. ignore: [/stories/, /src/] @@ -36,7 +36,7 @@ module.exports = { resolve: 'gatsby-source-filesystem', options: { name: 'packages', - path: '../../packages' + path: '../packages' }, }, // Handles markdown files (creates "MarkdownRemark" nodes) From c83451a5f056f8ab1a1a68eade00798427effb68 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 10:57:25 +0100 Subject: [PATCH 205/760] Update circle configuration with new docs website path --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7aecb0bcf..4a1cee905 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,7 +26,7 @@ references: cache_keys_docs: &cache_keys_docs keys: - - cache-docs-v1-{{ .Branch }}-{{ checksum "./tools/x-docs/package.json" }} + - cache-docs-v1-{{ .Branch }}-{{ checksum "./web/package.json" }} # # Cache creation @@ -39,9 +39,9 @@ references: create_cache_docs: &create_cache_docs save_cache: - key: cache-docs-v1-{{ .Branch }}-{{ checksum "./tools/x-docs/package.json" }} + key: cache-docs-v1-{{ .Branch }}-{{ checksum "./web/package.json" }} paths: - - ./tools/x-docs/node_modules/ + - ./web/node_modules/ # # Cache restoration From 785df564fa8512fc3484a87ac43809515fff6b27 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 10:57:48 +0100 Subject: [PATCH 206/760] Update eslint configuration with new website path --- .eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index c29184fe1..25a743b72 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,4 +6,4 @@ **/public/** **/public-prod/** **/blueprints/** -tools/x-docs/static/** \ No newline at end of file +web/static/** \ No newline at end of file From ae976146817a18b41e0d0cca38230450dbaa699e Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 10:59:04 +0100 Subject: [PATCH 207/760] Update documentation website instructions to reflect new docs site location --- docs/get-started/working-with-x-dash.md | 5 +++-- package.json | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/get-started/working-with-x-dash.md b/docs/get-started/working-with-x-dash.md index c57d52db9..9d73f26ea 100644 --- a/docs/get-started/working-with-x-dash.md +++ b/docs/get-started/working-with-x-dash.md @@ -30,11 +30,12 @@ The repository groups related code together in directories. UI components are st The documentation you're reading right now is generated from a Markdown file stored in the `docs` directory. Other pages to show the packages and components are created dynamically using information inferred from the repository. -This website is stored in the `tools/x-docs` directory and is built using the static site generator [Gatsby](https://gatsbyjs.org). You don't need to learn Gatsby to get started writing documentation! +This website is stored in the `web` directory and is built using the static site generator [Gatsby](https://gatsbyjs.org). You don't need to learn Gatsby to get started writing documentation! -Once you have [installed] the x-dash project you can run this command from the repository root to build and run the documentation website: +Once you have [installed] the x-dash project you can run this command from the repository root to install, build, and run the documentation website: ```sh +npm run install-docs npm run start-docs ``` diff --git a/package.json b/package.json index 972571734..907a263a1 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "blueprint": "node private/scripts/blueprint.js", "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", - "start-docs": "(cd tools/x-docs && npm start)", + "install-docs": "(cd web && npm install)", + "start-docs": "(cd web && npm start)", "heroku-postbuild": "make install && npm run build", "prepare": "npx snyk protect || npx snyk protect -d || true" }, From 806e3c40a7005099e01f06ce601e23c51f5f87c9 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 11:00:53 +0100 Subject: [PATCH 208/760] Update docs website CI deployment script to reflect new location --- private/scripts/gh-pages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/private/scripts/gh-pages b/private/scripts/gh-pages index f0d1b1fa5..4c9e9614c 100755 --- a/private/scripts/gh-pages +++ b/private/scripts/gh-pages @@ -1,6 +1,6 @@ #!/usr/bin/env bash -TARGET_DIR=tools/x-docs/public/* +TARGET_DIR=web/public/* TARGET_BRANCH=gh-pages TEMP_DIR=tmp From 86483171b0c33de2fd535399d0f2ddb363a865de Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 11:05:13 +0100 Subject: [PATCH 209/760] Update circle configuration to build docs website only when required --- .circleci/config.yml | 10 ++++++++-- package.json | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4a1cee905..710bd5cfb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,7 +93,6 @@ jobs: name: Checkout next-ci-shared-helpers command: git clone --depth 1 git@github.com:Financial-Times/next-ci-shared-helpers.git .circleci/shared-helpers - *restore_cache_root - - *restore_cache_docs - run: name: Install project dependencies command: make install @@ -101,7 +100,6 @@ jobs: name: Run the project build task command: make build - *create_cache_root - - *create_cache_docs - persist_to_workspace: root: *workspace_root paths: @@ -162,6 +160,14 @@ jobs: - add_ssh_keys: fingerprints: - "2b:98:17:21:34:bf:5d:3b:15:a5:82:77:90:11:03:e9" + - *restore_cache_docs + - run: + name: Install documentation website dependencies + command: npm run install-docs + - *create_cache_docs + - run: + name: Build documentation website + command: npm run build-docs - run: name: Publish GitHub Pages command: ./private/scripts/gh-pages diff --git a/package.json b/package.json index 907a263a1..4b2866880 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", "install-docs": "(cd web && npm install)", "start-docs": "(cd web && npm start)", + "build-docs": "(cd web && npm build)", "heroku-postbuild": "make install && npm run build", "prepare": "npx snyk protect || npx snyk protect -d || true" }, From 7c9593939f7b34574a48cbe61d6d36a3886838cb Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 11:08:05 +0100 Subject: [PATCH 210/760] Update all docs site dependencies --- web/.npmrc | 1 + web/package.json | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 web/.npmrc diff --git a/web/.npmrc b/web/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/web/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/web/package.json b/web/package.json index b36429da0..b1d938aa6 100644 --- a/web/package.json +++ b/web/package.json @@ -10,19 +10,19 @@ }, "license": "ISC", "devDependencies": { - "@financial-times/x-logo": "file:../../packages/x-logo", + "@financial-times/x-logo": "file:../packages/x-logo", "case": "^1.5.5", - "gatsby": "^2.0.11", - "gatsby-remark-autolink-headers": "^2.0.6", + "gatsby": "^2.9.8", + "gatsby-remark-autolink-headers": "^2.0.18", "gatsby-remark-external-links": "0.0.4", - "gatsby-remark-prismjs": "^3.0.1", - "gatsby-source-filesystem": "^2.0.2", - "gatsby-transformer-remark": "^2.1.6", - "gatsby-transformer-yaml": "^2.1.1", - "graphql-type-json": "^0.2.1", - "prismjs": "^1.15.0", - "react": "^16.4.1", - "react-dom": "^16.4.1", + "gatsby-remark-prismjs": "^3.2.10", + "gatsby-source-filesystem": "^2.0.42", + "gatsby-transformer-remark": "^2.4.0", + "gatsby-transformer-yaml": "^2.1.12", + "graphql-type-json": "^0.3.0", + "prismjs": "^1.16.0", + "react": "^16.8.6", + "react-dom": "^16.8.6", "react-helmet": "^5.2.0" }, "browserslist": [ From c9ff1e58234eff5e41821ee54b89fa560371aec9 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 11:11:39 +0100 Subject: [PATCH 211/760] Update storybook static output symlink in docs site --- web/static/storybook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/static/storybook b/web/static/storybook index 541a6f86c..76934145d 120000 --- a/web/static/storybook +++ b/web/static/storybook @@ -1 +1 @@ -../../../dist/storybook/ \ No newline at end of file +../../dist/storybook/ \ No newline at end of file From e59c125c98a9b1af2315109cd4335e281a3952b3 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 11:45:52 +0100 Subject: [PATCH 212/760] Remove redundant story node creation from docs site build --- .../on-create-node.js | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/web/plugins/gatsby-transformer-npm-package/on-create-node.js b/web/plugins/gatsby-transformer-npm-package/on-create-node.js index 9370a734b..9732f99a1 100644 --- a/web/plugins/gatsby-transformer-npm-package/on-create-node.js +++ b/web/plugins/gatsby-transformer-npm-package/on-create-node.js @@ -29,27 +29,4 @@ module.exports = ({ node, actions }) => { createNode(npmPackageNode); createParentChildLink({ parent: node, child: npmPackageNode }); } - - const storybookFiles = ['stories/index.js', 'storybook/index.js'] - - if (node.internal.type === 'File' && storybookFiles.some((filename) => node.absolutePath.endsWith(filename))) { - const contents = require(node.absolutePath); - - const storiesNode = { - id: `${node.id} >>> Stories`, - children: [], - parent: node.id, - internal: { - type: 'Stories' - }, - stories: contents.stories.map((story) => story.title), - fileAbsolutePath: node.absolutePath - }; - - // Append unique node hash - storiesNode.internal.contentDigest = hash(JSON.stringify(storiesNode)); - - createNode(storiesNode); - createParentChildLink({ parent: node, child: storiesNode }); - } }; From 60e3c426c8f133106cbf45e264c8c221d5ccfdd9 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 19 Jun 2019 11:49:06 +0100 Subject: [PATCH 213/760] Update npm package page creation story file check to include multiple file locations --- web/src/lib/create-npm-package-pages.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/web/src/lib/create-npm-package-pages.js b/web/src/lib/create-npm-package-pages.js index cd331da56..a2bfe926b 100644 --- a/web/src/lib/create-npm-package-pages.js +++ b/web/src/lib/create-npm-package-pages.js @@ -24,6 +24,15 @@ module.exports = async (actions, graphql) => { throw result.errors; } + function hasStorybookConfig(relPath) { + const locations = ['stories/index.js', 'storybook/index.js', 'storybook/index.jsx']; + + return locations.some((location) => { + const filePath = path.join(relPath, location); + return fs.existsSync(filePath); + }); + } + result.data.npmPackages.edges.map(({ node }) => { // Package manifest slug will be /package so remove it const pagePath = path.dirname(node.fields.slug); @@ -41,7 +50,7 @@ module.exports = async (actions, graphql) => { packageName: node.manifest.name, packageDescription: node.manifest.description, // Flag if Storybook demos are available for this package - storybook: fs.existsSync(path.join(relPath, 'stories', 'index.js')), + storybook: hasStorybookConfig(relPath), // Associate readme and story nodes via slug packagePath: path.join(pagePath, 'package'), readmePath: path.join(pagePath, 'readme') From 5f9f09a0bae2e5a887824567c4e35be1504e2071 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 10 Jul 2019 17:38:47 +0100 Subject: [PATCH 214/760] Fix bad docs build command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b2866880..d8e13a30c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", "install-docs": "(cd web && npm install)", "start-docs": "(cd web && npm start)", - "build-docs": "(cd web && npm build)", + "build-docs": "(cd web && npm run build)", "heroku-postbuild": "make install && npm run build", "prepare": "npx snyk protect || npx snyk protect -d || true" }, From 440d91dbe8c2c426bf00aa12558dd19702f9ca51 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 10 Jul 2019 18:16:10 +0100 Subject: [PATCH 215/760] Fix import of JSON GraphQL type as .default is no longer implied --- .../gatsby-transformer-npm-package/extend-node-type.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/plugins/gatsby-transformer-npm-package/extend-node-type.js b/web/plugins/gatsby-transformer-npm-package/extend-node-type.js index f521d1b26..3ff79d7ee 100644 --- a/web/plugins/gatsby-transformer-npm-package/extend-node-type.js +++ b/web/plugins/gatsby-transformer-npm-package/extend-node-type.js @@ -1,11 +1,11 @@ -const GraphQlJson = require('graphql-type-json'); +const { GraphQLJSONObject } = require('graphql-type-json'); // This allows us to fetch the entire manifest without specifying every field \0/ module.exports = ({ type }) => { if (type.name === 'NpmPackage') { return { manifest: { - type: GraphQlJson, + type: GraphQLJSONObject, resolve: (node) => node.manifest } }; From eb4343f59ebb9425ab4f71afe5a8c7e8041d79d5 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 10 Jul 2019 18:16:43 +0100 Subject: [PATCH 216/760] Add pattern to ignore all storybook source files from Gatsby filesystem plugin --- web/gatsby-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/gatsby-config.js b/web/gatsby-config.js index c3c04848c..d75338486 100644 --- a/web/gatsby-config.js +++ b/web/gatsby-config.js @@ -29,7 +29,7 @@ module.exports = { path: '../components', // Don't attempt to load any Storybook or source files, as these may // contain syntax and/or features we cannot parse. - ignore: [/stories/, /src/] + ignore: [/stories/, /storybook/, /src/] }, }, { From 8e2b3bc10a80f6e4ee3531fa3f6f136298ea00df Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Wed, 10 Jul 2019 18:22:24 +0100 Subject: [PATCH 217/760] Replace storybook symlink in gatsby static folder as files are being randomly removed --- web/.gitignore | 1 + web/package.json | 3 ++- web/static/storybook | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 120000 web/static/storybook diff --git a/web/.gitignore b/web/.gitignore index a1610c548..d5f088f78 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -1,2 +1,3 @@ .cache /public +/static/storybook diff --git a/web/package.json b/web/package.json index b1d938aa6..d2893c5b1 100644 --- a/web/package.json +++ b/web/package.json @@ -4,7 +4,8 @@ "version": "0.0.0", "description": "", "scripts": { - "build": "gatsby build --prefix-paths --verbose", + "prebuild": "[ -d '../dist/storybook' ] && cp -r ../dist/storybook static/storybook", + "build": "npm run prebuild && gatsby build --prefix-paths --verbose", "start": "gatsby develop -H local.ft.com", "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/web/static/storybook b/web/static/storybook deleted file mode 120000 index 76934145d..000000000 --- a/web/static/storybook +++ /dev/null @@ -1 +0,0 @@ -../../dist/storybook/ \ No newline at end of file From f9354707f0835520916bd56223ec5e5414187633 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Mon, 15 Jul 2019 18:54:37 +0100 Subject: [PATCH 218/760] Fix repo root path when generating Gatsby links, fixes #357 --- web/src/lib/decorate-nodes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/lib/decorate-nodes.js b/web/src/lib/decorate-nodes.js index 6de6662c3..a677c511d 100644 --- a/web/src/lib/decorate-nodes.js +++ b/web/src/lib/decorate-nodes.js @@ -2,7 +2,7 @@ const path = require('path'); const nodeTypesToSlug = new Set(['MarkdownRemark', 'NpmPackage']); -const repoRoot = path.resolve('../../'); +const repoRoot = path.resolve('../'); const createSlug = (file) => { const pathFromRoot = path.relative(repoRoot, file.absolutePath); From 390fb9d11ee4686d6b3eb3bbd76a8174cdbbb296 Mon Sep 17 00:00:00 2001 From: Asuka Ochi <asuka.ochi@ft.com> Date: Thu, 8 Aug 2019 11:57:08 +0100 Subject: [PATCH 219/760] Modify babel config to allow to module config for tests (#291) --- packages/x-babel-config/index.js | 4 ++-- packages/x-babel-config/jest.js | 7 ++++++- packages/x-rollup/src/rollup-config.js | 21 ++++++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/x-babel-config/index.js b/packages/x-babel-config/index.js index c02bb8186..7ef8d01b1 100644 --- a/packages/x-babel-config/index.js +++ b/packages/x-babel-config/index.js @@ -1,4 +1,4 @@ -module.exports = (targets = []) => ({ +module.exports = ({ targets = [], modules = false } = {}) => ({ plugins: [ // this plugin is not React specific! It includes a general JSX parser and helper 🙄 [ @@ -24,7 +24,7 @@ module.exports = (targets = []) => ({ require.resolve('@babel/preset-env'), { targets, - modules: false, + modules, exclude: ['transform-regenerator', 'transform-async-to-generator'] } ] diff --git a/packages/x-babel-config/jest.js b/packages/x-babel-config/jest.js index 5f61b4dcd..b6451ee82 100644 --- a/packages/x-babel-config/jest.js +++ b/packages/x-babel-config/jest.js @@ -1,4 +1,9 @@ const getBabelConfig = require('./'); const babelJest = require('babel-jest'); -module.exports = babelJest.createTransformer(getBabelConfig()); +const base = getBabelConfig({ + targets: [{ node: 'current' }], + modules: 'commonjs' +}); + +module.exports = babelJest.createTransformer(base); diff --git a/packages/x-rollup/src/rollup-config.js b/packages/x-rollup/src/rollup-config.js index a3b85dd67..0c652de13 100644 --- a/packages/x-rollup/src/rollup-config.js +++ b/packages/x-rollup/src/rollup-config.js @@ -25,7 +25,12 @@ module.exports = ({ input, pkg }) => { { input, external, - plugins: [babel(babelConfig({ node: 6 })), ...plugins] + plugins: [ + babel(babelConfig({ + targets: [{ node: 6 }] + })), + ...plugins + ] }, { file: pkg.module, @@ -36,7 +41,12 @@ module.exports = ({ input, pkg }) => { { input, external, - plugins: [babel(babelConfig({ node: 6 })), ...plugins] + plugins: [ + babel(babelConfig({ + targets: [{ node: 6 }] + })), + ...plugins + ] }, { file: pkg.main, @@ -47,7 +57,12 @@ module.exports = ({ input, pkg }) => { { input, external, - plugins: [babel(babelConfig({ browsers: ['ie 11'] })), ...plugins] + plugins: [ + babel(babelConfig({ + targets: [{ browsers: ['ie 11'] }] + })), + ...plugins + ] }, { file: pkg.browser, From 1eb42804889cc1fb29818f9dda9df89d8f3faef2 Mon Sep 17 00:00:00 2001 From: Rob Squires <rob.squires@ft.com> Date: Wed, 28 Aug 2019 15:45:43 +0100 Subject: [PATCH 220/760] Add x-podcast-launchers (#378) --- .storybook/register-components.js | 1 + .../__snapshots__/snapshots.test.js.snap | 116 ++++++++++++++++++ components/x-podcast-launchers/.bowerrc | 8 ++ components/x-podcast-launchers/.npmignore | 3 + components/x-podcast-launchers/bower.json | 9 ++ components/x-podcast-launchers/package.json | 40 ++++++ components/x-podcast-launchers/readme.md | 69 +++++++++++ components/x-podcast-launchers/rollup.js | 4 + .../src/PodcastLaunchers.jsx | 106 ++++++++++++++++ .../src/PodcastLaunchers.scss | 52 ++++++++ .../src/__tests__/PodcastLaunchers.test.jsx | 23 ++++ .../PodcastLaunchers.test.jsx.snap | 105 ++++++++++++++++ .../generate-app-links.test.js.snap | 41 +++++++ .../src/__tests__/generate-app-links.test.js | 11 ++ .../src/__tests__/generate-rss-url.test.js | 21 ++++ .../src/config/app-links.js | 33 +++++ .../src/config/series-ids.js | 5 + .../src/copy-to-clipboard.js | 21 ++++ .../src/generate-app-links.js | 13 ++ .../src/generate-rss-url.js | 8 ++ .../x-podcast-launchers/stories/example.js | 16 +++ .../x-podcast-launchers/stories/index.js | 19 +++ .../x-podcast-launchers/stories/knobs.js | 5 + 23 files changed, 729 insertions(+) create mode 100644 components/x-podcast-launchers/.bowerrc create mode 100644 components/x-podcast-launchers/.npmignore create mode 100644 components/x-podcast-launchers/bower.json create mode 100644 components/x-podcast-launchers/package.json create mode 100644 components/x-podcast-launchers/readme.md create mode 100644 components/x-podcast-launchers/rollup.js create mode 100644 components/x-podcast-launchers/src/PodcastLaunchers.jsx create mode 100644 components/x-podcast-launchers/src/PodcastLaunchers.scss create mode 100644 components/x-podcast-launchers/src/__tests__/PodcastLaunchers.test.jsx create mode 100644 components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap create mode 100644 components/x-podcast-launchers/src/__tests__/__snapshots__/generate-app-links.test.js.snap create mode 100644 components/x-podcast-launchers/src/__tests__/generate-app-links.test.js create mode 100644 components/x-podcast-launchers/src/__tests__/generate-rss-url.test.js create mode 100644 components/x-podcast-launchers/src/config/app-links.js create mode 100644 components/x-podcast-launchers/src/config/series-ids.js create mode 100644 components/x-podcast-launchers/src/copy-to-clipboard.js create mode 100644 components/x-podcast-launchers/src/generate-app-links.js create mode 100644 components/x-podcast-launchers/src/generate-rss-url.js create mode 100644 components/x-podcast-launchers/stories/example.js create mode 100644 components/x-podcast-launchers/stories/index.js create mode 100644 components/x-podcast-launchers/stories/knobs.js diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 3421fb438..06ad9288b 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -4,6 +4,7 @@ const components = [ require('../components/x-teaser/storybook'), require('../components/x-styling-demo/stories'), require('../components/x-gift-article/stories'), + require('../components/x-podcast-launchers/stories'), ]; module.exports = components; diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 19d78391b..7b5256a3c 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -554,6 +554,122 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits </div> `; +exports[`@financial-times/x-podcast-launchers renders a default Example x-podcast-launchers 1`] = ` +<div + className="PodcastLaunchers_container__1418-" +> + <h2 + className="PodcastLaunchers_heading__1103H" + > + Subscribe on a podcast app + </h2> + <ul + className="PodcastLaunchers_podcastAppLinksWrapper__7W6dn" + > + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + data-trackable="apple-podcasts" + href="podcast://access.acast.com/rss/therachmanreview/abc-123" + > + Apple Podcasts + </a> + </li> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + data-trackable="overcast" + href="overcast://x-callback-url/add?url=https://access.acast.com/rss/therachmanreview/abc-123" + > + Overcast + </a> + </li> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + data-trackable="pocket-casts" + href="pktc://subscribe/access.acast.com/rss/therachmanreview/abc-123" + > + Pocket Casts + </a> + </li> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + data-trackable="podcast-addict" + href="podcastaddict://access.acast.com/rss/therachmanreview/abc-123" + > + Podcast Addict + </a> + </li> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + data-trackable="acast" + href="acast://subscribe/https://access.acast.com/rss/therachmanreview/abc-123" + > + Acast + </a> + </li> + </ul> + <div + className="o-forms__affix-wrapper PodcastLaunchers_rssUrlWrapper__vc1zt" + > + <input + className="o-forms__text PodcastLaunchers_rssUrlInput__O7wL-" + readOnly={true} + type="text" + value="https://access.acast.com/rss/therachmanreview/abc-123" + /> + <div + className="o-forms__suffix PodcastLaunchers_rssUrlCopyButton__2_4c0" + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + data-url="https://access.acast.com/rss/therachmanreview/abc-123" + onClick={[Function]} + type="button" + > + Copy RSS + </button> + </div> + </div> + <h2 + className="PodcastLaunchers_heading__1103H" + > + Can’t see your podcast app? + </h2> + <form + action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" + data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" + method="GET" + onSubmit={[Function]} + > + <input + data-myft-csrf-token={true} + name="token" + type="hidden" + value="token" + /> + <button + aria-label="Add Rachman Review to myFT" + aria-pressed="false" + className="main_button__3Mk67" + dangerouslySetInnerHTML={ + Object { + "__html": "Add to myFT", + } + } + data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" + data-trackable="follow" + data-trackable-context-messaging={null} + title="Add Rachman Review to myFT" + type="submit" + /> + </form> +</div> +`; + exports[`@financial-times/x-styling-demo renders a default Styling x-styling-demo 1`] = ` <button className="Button_button__vS7Mv" diff --git a/components/x-podcast-launchers/.bowerrc b/components/x-podcast-launchers/.bowerrc new file mode 100644 index 000000000..39039a4a1 --- /dev/null +++ b/components/x-podcast-launchers/.bowerrc @@ -0,0 +1,8 @@ +{ + "registry": { + "search": [ + "https://origami-bower-registry.ft.com", + "https://registry.bower.io" + ] + } +} diff --git a/components/x-podcast-launchers/.npmignore b/components/x-podcast-launchers/.npmignore new file mode 100644 index 000000000..a44a9e753 --- /dev/null +++ b/components/x-podcast-launchers/.npmignore @@ -0,0 +1,3 @@ +src/ +stories/ +rollup.js diff --git a/components/x-podcast-launchers/bower.json b/components/x-podcast-launchers/bower.json new file mode 100644 index 000000000..b7c3e50cb --- /dev/null +++ b/components/x-podcast-launchers/bower.json @@ -0,0 +1,9 @@ +{ + "name": "@financial-times/x-podcast-launchers", + "description": "", + "main": "dist/PodcastLaunchers.es5.js", + "private": true, + "dependencies": { + "o-typography": "^5.12.0" + } +} diff --git a/components/x-podcast-launchers/package.json b/components/x-podcast-launchers/package.json new file mode 100644 index 000000000..4a57cbdf6 --- /dev/null +++ b/components/x-podcast-launchers/package.json @@ -0,0 +1,40 @@ +{ + "name": "@financial-times/x-podcast-launchers", + "version": "0.0.0", + "description": "", + "main": "dist/PodcastLaunchers.cjs.js", + "module": "dist/PodcastLaunchers.esm.js", + "browser": "dist/PodcastLaunchers.es5.js", + "style": "dist/PodcastLaunchers.css", + "scripts": { + "prepare": "bower install && npm run build", + "build": "node rollup.js", + "start": "node rollup.js --watch" + }, + "keywords": [ + "x-dash" + ], + "author": "", + "license": "ISC", + "dependencies": { + "@financial-times/n-concept-ids": "^1.4.0", + "@financial-times/x-engine": "file:../../packages/x-engine", + "@financial-times/x-follow-button": "0.0.12", + "nanoid": "^2.0.3" + }, + "devDependencies": { + "@financial-times/x-rollup": "file:../../packages/x-rollup", + "bower": "^1.8.8" + }, + "repository": { + "type": "git", + "url": "https://github.com/Financial-Times/x-dash.git" + }, + "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-podcastlaunchers", + "engines": { + "node": ">= 6.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/components/x-podcast-launchers/readme.md b/components/x-podcast-launchers/readme.md new file mode 100644 index 000000000..8f5ebaceb --- /dev/null +++ b/components/x-podcast-launchers/readme.md @@ -0,0 +1,69 @@ +# x-podcast-launchers + +This module allows users to open a podcast series in various podcast apps. The subscribe urls for each podcast app are generated from rss url with config (`/src/app-links.js`). + +No elements are returned when the `conceptId` does not map to a known podcast series, as defined in `src/map-concept-to-acast-series.js`. + +This component also renders a myFT follow button (x-follow-button) for the conceptId provided. This is acts as an onsite way to follow the series should the user's podcast app not be listed. + +![screenshot of x-podcast-launchers](https://user-images.githubusercontent.com/21194161/63341610-86996080-c341-11e9-8a21-04da9c8bb6cc.png) + +## Installation + +This module is compatible with Node 6+ and is distributed on npm. + +```bash +npm install --save @financial-times/x-podcast-launchers +``` + +The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. + +[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine + +## Styling + +To get correct styling, Your app should have origami components below. +[o-typography](https://registry.origami.ft.com/components/o-typography) +[o-buttons](https://registry.origami.ft.com/components/o-buttons) v5 +:memo: Only needs its `primary` theme classes +``` +$o-buttons-themes: ( + primary: 'primary', +); +``` +[o-forms](https://registry.origami.ft.com/components/o-forms) v6 (v7 above breaks the styling) +:memo: Only uses the text input with suffix button style classes + +## Usage + +The components provided by this module are all functions that expect a map of [properties](#properties). They can be used with vanilla JavaScript or JSX (If you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: + +```jsx +import React from 'react'; +import { PodcastLaunchers } from '@financial-times/x-podcast-launchers'; + +// A == B == C +const a = PodcastLaunchers(props); +const b = <PodcastLaunchers {...props} />; +const c = React.createElement(PodcastLaunchers, props); +``` + +```scss +// within your app's sass file +@import "@financial-times/x-podcast-launchers/dist/PodcastLaunchers"; +``` +:warning: This component depends on styles provided by o-forms and o-buttons, and therefore o-forms and o-buttons needs to be imported before x-podcast-launchers. + +All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. + +[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ + +### Properties + +Feature | Type | Required | Notes +---------------------|----------|----------|------------------ +`acastRSSHost` | String | Yes | e.g. 'https://acast.access.com' +`conceptId` | String | Yes | +`renderFollowButton` | Function | No | Optional render prop for the follow button + +Additional props such as the `conceptName` may be required by x-follow-button. Documentation for these is available over in the component's readme. diff --git a/components/x-podcast-launchers/rollup.js b/components/x-podcast-launchers/rollup.js new file mode 100644 index 000000000..167dadc15 --- /dev/null +++ b/components/x-podcast-launchers/rollup.js @@ -0,0 +1,4 @@ +const xRollup = require('@financial-times/x-rollup'); +const pkg = require('./package.json'); + +xRollup({ input: './src/PodcastLaunchers.jsx', pkg }); diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.jsx b/components/x-podcast-launchers/src/PodcastLaunchers.jsx new file mode 100644 index 000000000..ce630c187 --- /dev/null +++ b/components/x-podcast-launchers/src/PodcastLaunchers.jsx @@ -0,0 +1,106 @@ +import { h, Component } from '@financial-times/x-engine'; +import { FollowButton } from '@financial-times/x-follow-button'; +import generateAppLinks from './generate-app-links'; +import generateRSSUrl from './generate-rss-url'; +import acastSeriesIds from './config/series-ids'; +import styles from './PodcastLaunchers.scss'; +import copyToClipboard from './copy-to-clipboard'; + +const basicButtonStyles = [ + 'o-buttons', + 'o-buttons--primary', + 'o-buttons--big' +].join(' '); + +const podcastAppLinkStyles = [ + basicButtonStyles, + styles.podcastAppLink +].join(' '); + +const rssUrlWrapperStyles = [ + 'o-forms__affix-wrapper', + styles.rssUrlWrapper +].join(' '); + +const rssUrlInputStyles = [ + 'o-forms__text', + styles.rssUrlInput +].join(' '); + +const rssUrlCopyButtonWrapperStyles = [ + 'o-forms__suffix', + styles.rssUrlCopyButton +].join(' '); + + +function defaultFollowButtonRender (conceptId, conceptName, csrfToken, isFollowed) { + return ( + <FollowButton + conceptId={conceptId} + conceptName={conceptName} + csrfToken={csrfToken} + isFollowed={isFollowed} + />); +} + +class PodcastLaunchers extends Component { + constructor(props) { + super(props); + this.state = { + rssUrl: '' + } + } + + componentDidMount() { + const { conceptId, acastRSSHost, acastAccessToken } = this.props; + const acastSeries = acastSeriesIds.get(conceptId); + if (acastSeries) { + this.setState({ + rssUrl: generateRSSUrl(acastRSSHost, acastSeries, acastAccessToken) + }); + } + + } + + render() { + const { rssUrl } = this.state; + const { conceptId, conceptName, csrfToken, isFollowed, renderFollowButton } = this.props + const followButton = typeof renderFollowButton === 'function' ? renderFollowButton : defaultFollowButtonRender; + + return rssUrl && ( + <div className={styles.container}> + <h2 className={styles.heading}>Subscribe on a podcast app</h2> + <ul className={styles.podcastAppLinksWrapper}> + {generateAppLinks(rssUrl).map(({ name, url, trackingId }) => ( + <li key={name}> + <a + href={url} + className={podcastAppLinkStyles} + data-trackable={trackingId}> + {name} + </a> + </li> + ))} + </ul> + + <div className={rssUrlWrapperStyles}> + <input className={rssUrlInputStyles} value={rssUrl} type='text' readOnly/> + <div className={rssUrlCopyButtonWrapperStyles}> + <button + className={basicButtonStyles} + onClick={copyToClipboard} + data-url={rssUrl} + type='button'> + Copy RSS + </button> + </div> + </div> + + <h2 className={styles.heading}>Can’t see your podcast app?</h2> + {followButton(conceptId, conceptName, csrfToken, isFollowed)} + </div> + ) + } +} + +export { PodcastLaunchers }; diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.scss b/components/x-podcast-launchers/src/PodcastLaunchers.scss new file mode 100644 index 000000000..841e5e5df --- /dev/null +++ b/components/x-podcast-launchers/src/PodcastLaunchers.scss @@ -0,0 +1,52 @@ +$o-typography-is-silent: true; +@import 'o-typography/main'; + +:global { + @import "~@financial-times/x-follow-button/dist/FollowButton"; +} + +.container { + width: 100%; +} + +.heading { + @include oTypographySansBold($scale: 1); + margin: 0 0 12px; +} + +.podcastAppLinksWrapper { + list-style: none; + padding: 8px 0 0; + margin: 0; +} + +.podcastAppLink { + width: 100%; + margin-bottom: 8px; +} + +.rssUrlWrapper { + margin-top: 0px; + margin-bottom: 24px; +} + +.rssUrlInput { + max-width: none; +} + +.rssUrlCopyButton { + padding-left: 0px; +} + +.rssUrlCopySpan { + user-select: text; + position: absolute; + clip: rect(0 0 0 0); + margin: -1px; + border: 0; + overflow: hidden; + padding: 0; + width: 1px; + height: 1px; + white-space: nowrap; +} diff --git a/components/x-podcast-launchers/src/__tests__/PodcastLaunchers.test.jsx b/components/x-podcast-launchers/src/__tests__/PodcastLaunchers.test.jsx new file mode 100644 index 000000000..e7f9e695a --- /dev/null +++ b/components/x-podcast-launchers/src/__tests__/PodcastLaunchers.test.jsx @@ -0,0 +1,23 @@ +import { h } from '@financial-times/x-engine'; +import { brand } from '@financial-times/n-concept-ids'; +import renderer from 'react-test-renderer'; +jest.mock('../PodcastLaunchers.scss', () => ({})); +import { PodcastLaunchers } from '../PodcastLaunchers'; + + +const acastRSSHost = 'https://acast.access'; +const acastAccessToken = '123-abc'; + +describe('PodcastLaunchers', () => { + it('should hide itself if an RSS URL could not be generated', () => { + expect( + renderer.create(<PodcastLaunchers conceptId='123-abc' {...{acastRSSHost, acastAccessToken}} />).toJSON() + ).toMatchSnapshot(); + }); + + it('should render the app links based on concept Id', () => { + expect( + renderer.create(<PodcastLaunchers conceptId={brand.rachmanReviewPodcast} {...{acastRSSHost, acastAccessToken}} />).toJSON() + ).toMatchSnapshot(); + }); +}) diff --git a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap new file mode 100644 index 000000000..0fea4c9e7 --- /dev/null +++ b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PodcastLaunchers should hide itself if an RSS URL could not be generated 1`] = `""`; + +exports[`PodcastLaunchers should render the app links based on concept Id 1`] = ` +<div> + <h2> + Subscribe on a podcast app + </h2> + <ul> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big " + data-trackable="apple-podcasts" + href="podcast://acast.access/rss/therachmanreview/123-abc" + > + Apple Podcasts + </a> + </li> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big " + data-trackable="overcast" + href="overcast://x-callback-url/add?url=https://acast.access/rss/therachmanreview/123-abc" + > + Overcast + </a> + </li> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big " + data-trackable="pocket-casts" + href="pktc://subscribe/acast.access/rss/therachmanreview/123-abc" + > + Pocket Casts + </a> + </li> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big " + data-trackable="podcast-addict" + href="podcastaddict://acast.access/rss/therachmanreview/123-abc" + > + Podcast Addict + </a> + </li> + <li> + <a + className="o-buttons o-buttons--primary o-buttons--big " + data-trackable="acast" + href="acast://subscribe/https://acast.access/rss/therachmanreview/123-abc" + > + Acast + </a> + </li> + </ul> + <div + className="o-forms__affix-wrapper " + > + <input + className="o-forms__text " + readOnly={true} + type="text" + value="https://acast.access/rss/therachmanreview/123-abc" + /> + <div + className="o-forms__suffix " + > + <button + className="o-buttons o-buttons--primary o-buttons--big" + data-url="https://acast.access/rss/therachmanreview/123-abc" + onClick={[Function]} + type="button" + > + Copy RSS + </button> + </div> + </div> + <h2> + Can’t see your podcast app? + </h2> + <form + action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" + data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Add undefined to myFT" + aria-pressed="false" + className="main_button__3Mk67" + dangerouslySetInnerHTML={ + Object { + "__html": "Add to myFT", + } + } + data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" + data-trackable="follow" + data-trackable-context-messaging={null} + title="Add undefined to myFT" + type="submit" + /> + </form> +</div> +`; diff --git a/components/x-podcast-launchers/src/__tests__/__snapshots__/generate-app-links.test.js.snap b/components/x-podcast-launchers/src/__tests__/__snapshots__/generate-app-links.test.js.snap new file mode 100644 index 000000000..01891d182 --- /dev/null +++ b/components/x-podcast-launchers/src/__tests__/__snapshots__/generate-app-links.test.js.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate-app-links should generate each app link with the urlencoded RSS url 1`] = ` +Array [ + Object { + "includeProtocol": false, + "name": "Apple Podcasts", + "template": "podcast://{url}", + "trackingId": "apple-podcasts", + "url": "podcast://acast.access/rss/ft-news/&£@1234", + }, + Object { + "includeProtocol": true, + "name": "Overcast", + "template": "overcast://x-callback-url/add?url={url}", + "trackingId": "overcast", + "url": "overcast://x-callback-url/add?url=https://acast.access/rss/ft-news/&£@1234", + }, + Object { + "includeProtocol": false, + "name": "Pocket Casts", + "template": "pktc://subscribe/{url}", + "trackingId": "pocket-casts", + "url": "pktc://subscribe/acast.access/rss/ft-news/&£@1234", + }, + Object { + "includeProtocol": false, + "name": "Podcast Addict", + "template": "podcastaddict://{url}", + "trackingId": "podcast-addict", + "url": "podcastaddict://acast.access/rss/ft-news/&£@1234", + }, + Object { + "includeProtocol": true, + "name": "Acast", + "template": "acast://subscribe/{url}", + "trackingId": "acast", + "url": "acast://subscribe/https://acast.access/rss/ft-news/&£@1234", + }, +] +`; diff --git a/components/x-podcast-launchers/src/__tests__/generate-app-links.test.js b/components/x-podcast-launchers/src/__tests__/generate-app-links.test.js new file mode 100644 index 000000000..1c81177b7 --- /dev/null +++ b/components/x-podcast-launchers/src/__tests__/generate-app-links.test.js @@ -0,0 +1,11 @@ + +import generateAppLinks from '../generate-app-links'; + +describe('generate-app-links', () => { + it('should generate each app link with the urlencoded RSS url', () => { + const rssUrl = 'https://acast.access/rss/ft-news/&£@1234'; + expect( + generateAppLinks(rssUrl) + ).toMatchSnapshot(); + }) +}) diff --git a/components/x-podcast-launchers/src/__tests__/generate-rss-url.test.js b/components/x-podcast-launchers/src/__tests__/generate-rss-url.test.js new file mode 100644 index 000000000..7eb6de461 --- /dev/null +++ b/components/x-podcast-launchers/src/__tests__/generate-rss-url.test.js @@ -0,0 +1,21 @@ + +import generateRSSUrl from '../generate-rss-url'; + +jest.mock('nanoid'); +import nanoid from 'nanoid'; + +describe('generate-app-links', () => { + it('returns the acast access url from showId and token', () => { + expect( + generateRSSUrl('https://access.acast.cloud', 'ft-news-extra', '123-456') + ).toEqual('https://access.acast.cloud/rss/ft-news-extra/123-456') + }); + + it('generates a random token if one is not provided', () => { + nanoid.mockImplementation(() => 'abc-123'); + + expect( + generateRSSUrl('https://access.acast.cloud', 'ft-news-extra') + ).toMatch('https://access.acast.cloud/rss/ft-news-extra/abc-123') + }); +}); diff --git a/components/x-podcast-launchers/src/config/app-links.js b/components/x-podcast-launchers/src/config/app-links.js new file mode 100644 index 000000000..4e2a20c2d --- /dev/null +++ b/components/x-podcast-launchers/src/config/app-links.js @@ -0,0 +1,33 @@ +export default +[ + { + "name": "Apple Podcasts", + "template" : "podcast://{url}", + "includeProtocol": false, + "trackingId": "apple-podcasts", + }, + { + "name": "Overcast", + "template": "overcast://x-callback-url/add?url={url}", + "includeProtocol": true, + "trackingId": "overcast", + }, + { + "name": "Pocket Casts", + "template": "pktc://subscribe/{url}", + "includeProtocol": false, + "trackingId": "pocket-casts", + }, + { + "name": "Podcast Addict", + "template": "podcastaddict://{url}", + "includeProtocol": false, + "trackingId": "podcast-addict", + }, + { + "name": "Acast", + "template" : "acast://subscribe/{url}", + "includeProtocol": true, + "trackingId": "acast", + } +] diff --git a/components/x-podcast-launchers/src/config/series-ids.js b/components/x-podcast-launchers/src/config/series-ids.js new file mode 100644 index 000000000..110edabc2 --- /dev/null +++ b/components/x-podcast-launchers/src/config/series-ids.js @@ -0,0 +1,5 @@ +import { brand } from '@financial-times/n-concept-ids'; + +export default new Map([ + [brand.rachmanReviewPodcast, 'therachmanreview'] +]); diff --git a/components/x-podcast-launchers/src/copy-to-clipboard.js b/components/x-podcast-launchers/src/copy-to-clipboard.js new file mode 100644 index 000000000..39f476f1b --- /dev/null +++ b/components/x-podcast-launchers/src/copy-to-clipboard.js @@ -0,0 +1,21 @@ +import styles from './PodcastLaunchers.scss'; + +export default function copyToClipboard (event) { + const url = event.target.dataset.url; + const containerEl = event.target.parentElement; + const rssLink = document.createElement('span'); + + rssLink.classList.add(styles.rssUrlCopySpan); + rssLink.appendChild(document.createTextNode(url)); + containerEl.appendChild(rssLink); + + const range = document.createRange(); + + window.getSelection().removeAllRanges(); + range.selectNode(rssLink); + window.getSelection().addRange(range); + document.execCommand('copy'); + + window.getSelection().removeAllRanges(); + containerEl.removeChild(rssLink); +} diff --git a/components/x-podcast-launchers/src/generate-app-links.js b/components/x-podcast-launchers/src/generate-app-links.js new file mode 100644 index 000000000..0dae6e651 --- /dev/null +++ b/components/x-podcast-launchers/src/generate-app-links.js @@ -0,0 +1,13 @@ +import appLinksConfig from './config/app-links'; + +export default function generateAppLinks(rssUrl) { + return appLinksConfig.map(data => { + const url = data.includeProtocol + ? rssUrl + : rssUrl.replace(/^https?:\/\//, ''); + + return Object.assign({}, data, { + url: data.template.replace(/{url}/, url) + }); + }); +} diff --git a/components/x-podcast-launchers/src/generate-rss-url.js b/components/x-podcast-launchers/src/generate-rss-url.js new file mode 100644 index 000000000..fb68447ed --- /dev/null +++ b/components/x-podcast-launchers/src/generate-rss-url.js @@ -0,0 +1,8 @@ +import nanoid from 'nanoid'; + +export default function generateRSSUrl(acastHost, seriesId, token) { + // the API needs any token for now, as it is a private RSS feed. + // If the experiment successful these tokens would eventually be tied to the user’s account with Membership + // ie. https://access.acast.cloud/rss/ft-test/tYPWWHla + return `${acastHost}/rss/${seriesId}/${token || nanoid(10)}`; +} diff --git a/components/x-podcast-launchers/stories/example.js b/components/x-podcast-launchers/stories/example.js new file mode 100644 index 000000000..a88465dd6 --- /dev/null +++ b/components/x-podcast-launchers/stories/example.js @@ -0,0 +1,16 @@ +const { brand } = require('@financial-times/n-concept-ids'); + +exports.title = 'Example'; + +exports.data = { + conceptId: brand.rachmanReviewPodcast, + conceptName: 'Rachman Review', + isFollowed: false, + csrfToken: 'token', + acastRSSHost: 'https://access.acast.com', + acastAccessToken: 'abc-123' +}; + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module; diff --git a/components/x-podcast-launchers/stories/index.js b/components/x-podcast-launchers/stories/index.js new file mode 100644 index 000000000..dee2e4ceb --- /dev/null +++ b/components/x-podcast-launchers/stories/index.js @@ -0,0 +1,19 @@ +const { PodcastLaunchers } = require('../'); + +exports.component = PodcastLaunchers; + +exports.package = require('../package.json'); + +// Set up basic document styling using the Origami build service +exports.dependencies = { + 'o-normalise': '^1.6.0', + 'o-typography': '^5.5.0', + 'o-buttons': '^5.16.6', + 'o-forms': '^6.0.3' +}; + +exports.stories = [ + require('./example') +]; + +exports.knobs = require('./knobs'); diff --git a/components/x-podcast-launchers/stories/knobs.js b/components/x-podcast-launchers/stories/knobs.js new file mode 100644 index 000000000..6d22e1ba1 --- /dev/null +++ b/components/x-podcast-launchers/stories/knobs.js @@ -0,0 +1,5 @@ +module.exports = (data, { text }) => ({ + conceptId: text('Concept id', data.conceptId), + acastRSSHost: text('Acast RSS host', data.acastRSSHost), + acastAccessToken: text('Acast Access token', data.acastAccessToken), +}) From 450eb9a6355a5d4633d4ff29307d339150b162cb Mon Sep 17 00:00:00 2001 From: Asuka Ochi <asuka.ochi@ft.com> Date: Wed, 28 Aug 2019 15:56:01 +0100 Subject: [PATCH 221/760] Update changelog.md --- changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.md b/changelog.md index 0a03508b3..d1bb34879 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,10 @@ ## v1 +### v1.0.0-beta.15 + +- Adds x-podcast-launchers (#378) + ### v1.0.0-beta.14 - Adds missing optimum video size configuration to x-teaser (#351) From fee3908c5e394431e2af59b436cf5a69f22ee4bf Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Thu, 29 Aug 2019 11:25:31 +0100 Subject: [PATCH 222/760] Add additional data trackables around podcast launchers (#392) * Add additional data trackables around podcast launchers * Move rss url elements into ul as li element * Modify margins --- .../__snapshots__/snapshots.test.js.snap | 44 ++++++++--------- .../src/PodcastLaunchers.jsx | 47 ++++++++++--------- .../src/PodcastLaunchers.scss | 9 ++-- .../PodcastLaunchers.test.jsx.snap | 47 ++++++++++--------- 4 files changed, 76 insertions(+), 71 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 7b5256a3c..74691c7b0 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -557,6 +557,7 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits exports[`@financial-times/x-podcast-launchers renders a default Example x-podcast-launchers 1`] = ` <div className="PodcastLaunchers_container__1418-" + data-trackable="podcast-launchers" > <h2 className="PodcastLaunchers_heading__1103H" @@ -611,29 +612,30 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas Acast </a> </li> - </ul> - <div - className="o-forms__affix-wrapper PodcastLaunchers_rssUrlWrapper__vc1zt" - > - <input - className="o-forms__text PodcastLaunchers_rssUrlInput__O7wL-" - readOnly={true} - type="text" - value="https://access.acast.com/rss/therachmanreview/abc-123" - /> - <div - className="o-forms__suffix PodcastLaunchers_rssUrlCopyButton__2_4c0" + <li + className="o-forms__affix-wrapper PodcastLaunchers_rssUrlWrapper__vc1zt" > - <button - className="o-buttons o-buttons--primary o-buttons--big" - data-url="https://access.acast.com/rss/therachmanreview/abc-123" - onClick={[Function]} - type="button" + <input + className="o-forms__text PodcastLaunchers_rssUrlInput__O7wL-" + readOnly={true} + type="text" + value="https://access.acast.com/rss/therachmanreview/abc-123" + /> + <div + className="o-forms__suffix PodcastLaunchers_rssUrlCopyButton__2_4c0" > - Copy RSS - </button> - </div> - </div> + <button + className="o-buttons o-buttons--primary o-buttons--big" + data-trackable="copy-rss" + data-url="https://access.acast.com/rss/therachmanreview/abc-123" + onClick={[Function]} + type="button" + > + Copy RSS + </button> + </div> + </li> + </ul> <h2 className="PodcastLaunchers_heading__1103H" > diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.jsx b/components/x-podcast-launchers/src/PodcastLaunchers.jsx index ce630c187..7a27d33fd 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.jsx +++ b/components/x-podcast-launchers/src/PodcastLaunchers.jsx @@ -68,34 +68,35 @@ class PodcastLaunchers extends Component { const followButton = typeof renderFollowButton === 'function' ? renderFollowButton : defaultFollowButtonRender; return rssUrl && ( - <div className={styles.container}> + <div className={styles.container} data-trackable='podcast-launchers'> <h2 className={styles.heading}>Subscribe on a podcast app</h2> <ul className={styles.podcastAppLinksWrapper}> - {generateAppLinks(rssUrl).map(({ name, url, trackingId }) => ( - <li key={name}> - <a - href={url} - className={podcastAppLinkStyles} - data-trackable={trackingId}> - {name} - </a> + {generateAppLinks(rssUrl).map(({ name, url, trackingId }) => ( + <li key={name}> + <a + href={url} + className={podcastAppLinkStyles} + data-trackable={trackingId}> + {name} + </a> + </li> + ))} + + <li key='Rss Url' className={rssUrlWrapperStyles}> + <input className={rssUrlInputStyles} value={rssUrl} type='text' readOnly/> + <div className={rssUrlCopyButtonWrapperStyles}> + <button + className={basicButtonStyles} + onClick={copyToClipboard} + data-url={rssUrl} + data-trackable='copy-rss' + type='button'> + Copy RSS + </button> + </div> </li> - ))} </ul> - <div className={rssUrlWrapperStyles}> - <input className={rssUrlInputStyles} value={rssUrl} type='text' readOnly/> - <div className={rssUrlCopyButtonWrapperStyles}> - <button - className={basicButtonStyles} - onClick={copyToClipboard} - data-url={rssUrl} - type='button'> - Copy RSS - </button> - </div> - </div> - <h2 className={styles.heading}>Can’t see your podcast app?</h2> {followButton(conceptId, conceptName, csrfToken, isFollowed)} </div> diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.scss b/components/x-podcast-launchers/src/PodcastLaunchers.scss index 841e5e5df..d46bb5be0 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.scss +++ b/components/x-podcast-launchers/src/PodcastLaunchers.scss @@ -16,18 +16,17 @@ $o-typography-is-silent: true; .podcastAppLinksWrapper { list-style: none; - padding: 8px 0 0; - margin: 0; + padding: 0; + margin: 0 0 24px; } .podcastAppLink { width: 100%; - margin-bottom: 8px; + margin-top: 8px; } .rssUrlWrapper { - margin-top: 0px; - margin-bottom: 24px; + margin-top: 8px; } .rssUrlInput { diff --git a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap index 0fea4c9e7..e357bd1f3 100644 --- a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap +++ b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap @@ -3,7 +3,9 @@ exports[`PodcastLaunchers should hide itself if an RSS URL could not be generated 1`] = `""`; exports[`PodcastLaunchers should render the app links based on concept Id 1`] = ` -<div> +<div + data-trackable="podcast-launchers" +> <h2> Subscribe on a podcast app </h2> @@ -53,29 +55,30 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = Acast </a> </li> - </ul> - <div - className="o-forms__affix-wrapper " - > - <input - className="o-forms__text " - readOnly={true} - type="text" - value="https://acast.access/rss/therachmanreview/123-abc" - /> - <div - className="o-forms__suffix " + <li + className="o-forms__affix-wrapper " > - <button - className="o-buttons o-buttons--primary o-buttons--big" - data-url="https://acast.access/rss/therachmanreview/123-abc" - onClick={[Function]} - type="button" + <input + className="o-forms__text " + readOnly={true} + type="text" + value="https://acast.access/rss/therachmanreview/123-abc" + /> + <div + className="o-forms__suffix " > - Copy RSS - </button> - </div> - </div> + <button + className="o-buttons o-buttons--primary o-buttons--big" + data-trackable="copy-rss" + data-url="https://acast.access/rss/therachmanreview/123-abc" + onClick={[Function]} + type="button" + > + Copy RSS + </button> + </div> + </li> + </ul> <h2> Can’t see your podcast app? </h2> From e9e1e6998b6e5f28254ab2502a5b8e7f8c19d372 Mon Sep 17 00:00:00 2001 From: Asuka Ochi <asuka.ochi@ft.com> Date: Thu, 29 Aug 2019 11:29:11 +0100 Subject: [PATCH 223/760] Add another PR number --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index d1bb34879..31e806e64 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ ### v1.0.0-beta.15 -- Adds x-podcast-launchers (#378) +- Adds x-podcast-launchers (#378)(#392) ### v1.0.0-beta.14 From 31ca332477f8cbd0e3ba49e0a04e5abe29987382 Mon Sep 17 00:00:00 2001 From: "Jennifer.Shepherd" <jennifer.shepherd@ft.com> Date: Thu, 29 Aug 2019 17:50:59 +0100 Subject: [PATCH 224/760] add aria label for categories --- components/x-teaser/src/MetaLink.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/x-teaser/src/MetaLink.jsx b/components/x-teaser/src/MetaLink.jsx index 819791e07..1d372daab 100644 --- a/components/x-teaser/src/MetaLink.jsx +++ b/components/x-teaser/src/MetaLink.jsx @@ -23,7 +23,8 @@ export default ({ metaPrefixText, metaLink, metaAltLink, metaSuffixText, context <a className="o-teaser__tag" data-trackable="teaser-tag" - href={displayLink.relativeUrl || displayLink.url}> + href={displayLink.relativeUrl || displayLink.url} + aria-label="Category: {displayLink.prefLabel}"> {displayLink.prefLabel} </a> ) : null} From a3f6d9fb44f7963f8049934cde2df8058cf091bc Mon Sep 17 00:00:00 2001 From: "Jennifer.Shepherd" <jennifer.shepherd@ft.com> Date: Thu, 29 Aug 2019 18:00:07 +0100 Subject: [PATCH 225/760] update test snapshots for aria-labels --- .../__snapshots__/snapshots.test.js.snap | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 99ebca3fb..fb1d94195 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -15,6 +15,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -78,6 +79,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -141,6 +143,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -209,6 +212,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] = FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -272,6 +276,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -406,6 +411,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -469,6 +475,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -537,6 +544,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with article data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -587,6 +595,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage da className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -637,6 +646,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -692,6 +702,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with packageItem data FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -742,6 +753,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with podcast data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -850,6 +862,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with topStory data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -900,6 +913,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with video data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -955,6 +969,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1018,6 +1033,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1081,6 +1097,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1149,6 +1166,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1212,6 +1230,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1346,6 +1365,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1409,6 +1429,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1477,6 +1498,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with article data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1515,6 +1537,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage dat className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1553,6 +1576,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1596,6 +1620,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with packageItem data 1 FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1634,6 +1659,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with podcast data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1718,6 +1744,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with topStory data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1756,6 +1783,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1853,6 +1881,7 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1928,6 +1957,7 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2003,6 +2033,7 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2083,6 +2114,7 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] = FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2158,6 +2190,7 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2316,6 +2349,7 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2391,6 +2425,7 @@ exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2471,6 +2506,7 @@ exports[`x-teaser / snapshots renders a Small teaser with article data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2509,6 +2545,7 @@ exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2547,6 +2584,7 @@ exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2590,6 +2628,7 @@ exports[`x-teaser / snapshots renders a Small teaser with packageItem data 1`] = FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2628,6 +2667,7 @@ exports[`x-teaser / snapshots renders a Small teaser with podcast data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2712,6 +2752,7 @@ exports[`x-teaser / snapshots renders a Small teaser with topStory data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2750,6 +2791,7 @@ exports[`x-teaser / snapshots renders a Small teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2793,6 +2835,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2868,6 +2911,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2943,6 +2987,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3023,6 +3068,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3098,6 +3144,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3256,6 +3303,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`] className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3331,6 +3379,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3411,6 +3460,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with article data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3461,6 +3511,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3511,6 +3562,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3566,6 +3618,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with packageItem data 1` FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3616,6 +3669,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with podcast data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3724,6 +3778,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with topStory data 1`] = className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3811,6 +3866,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3866,6 +3922,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3941,6 +3998,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4016,6 +4074,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4096,6 +4155,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte FT Series </span> <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4171,6 +4231,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4329,6 +4390,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4441,6 +4503,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with video data className="o-teaser__meta-tag" > <a + aria-label="Category: {displayLink.prefLabel}" className="o-teaser__tag" data-trackable="teaser-tag" href="#" From 8c0ad6233658ac4ec03015a42d594d34d0d749ab Mon Sep 17 00:00:00 2001 From: "Jennifer.Shepherd" <jennifer.shepherd@ft.com> Date: Fri, 30 Aug 2019 09:58:34 +0100 Subject: [PATCH 226/760] update the aria-label interpolated string --- components/x-teaser/src/MetaLink.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/src/MetaLink.jsx b/components/x-teaser/src/MetaLink.jsx index 1d372daab..dfee3893c 100644 --- a/components/x-teaser/src/MetaLink.jsx +++ b/components/x-teaser/src/MetaLink.jsx @@ -24,7 +24,7 @@ export default ({ metaPrefixText, metaLink, metaAltLink, metaSuffixText, context className="o-teaser__tag" data-trackable="teaser-tag" href={displayLink.relativeUrl || displayLink.url} - aria-label="Category: {displayLink.prefLabel}"> + aria-label={`Category: ${displayLink.prefLabel}`}> {displayLink.prefLabel} </a> ) : null} From 0b197bed12ab25edfa50e198cf3fbf8578aa67a5 Mon Sep 17 00:00:00 2001 From: "Jennifer.Shepherd" <jennifer.shepherd@ft.com> Date: Fri, 30 Aug 2019 10:05:18 +0100 Subject: [PATCH 227/760] update test snapshots for aria-labels --- .../__snapshots__/snapshots.test.js.snap | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index fb1d94195..9cb86f7c2 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -15,7 +15,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -79,7 +79,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -143,7 +143,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -212,7 +212,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] = FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -276,7 +276,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -411,7 +411,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -475,7 +475,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -544,7 +544,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with article data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -595,7 +595,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage da className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -646,7 +646,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -702,7 +702,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with packageItem data FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -753,7 +753,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with podcast data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -862,7 +862,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with topStory data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -913,7 +913,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with video data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -969,7 +969,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1033,7 +1033,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1097,7 +1097,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1166,7 +1166,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1230,7 +1230,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1365,7 +1365,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1429,7 +1429,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1498,7 +1498,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with article data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1537,7 +1537,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage dat className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1576,7 +1576,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1620,7 +1620,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with packageItem data 1 FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1659,7 +1659,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with podcast data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1744,7 +1744,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with topStory data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1783,7 +1783,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1881,7 +1881,7 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -1957,7 +1957,7 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2033,7 +2033,7 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2114,7 +2114,7 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] = FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2190,7 +2190,7 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2349,7 +2349,7 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2425,7 +2425,7 @@ exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2506,7 +2506,7 @@ exports[`x-teaser / snapshots renders a Small teaser with article data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2545,7 +2545,7 @@ exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2584,7 +2584,7 @@ exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2628,7 +2628,7 @@ exports[`x-teaser / snapshots renders a Small teaser with packageItem data 1`] = FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2667,7 +2667,7 @@ exports[`x-teaser / snapshots renders a Small teaser with podcast data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2752,7 +2752,7 @@ exports[`x-teaser / snapshots renders a Small teaser with topStory data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2791,7 +2791,7 @@ exports[`x-teaser / snapshots renders a Small teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2835,7 +2835,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2911,7 +2911,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -2987,7 +2987,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3068,7 +3068,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3144,7 +3144,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3303,7 +3303,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`] className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3379,7 +3379,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3460,7 +3460,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with article data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3511,7 +3511,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3562,7 +3562,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3618,7 +3618,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with packageItem data 1` FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3669,7 +3669,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with podcast data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3778,7 +3778,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with topStory data 1`] = className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3866,7 +3866,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with video data 1`] = ` className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3922,7 +3922,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -3998,7 +3998,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: FT Magazine" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4074,7 +4074,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Gideon Rachman" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4155,7 +4155,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte FT Series </span> <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Financial crisis: Are we safer now? " className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4231,7 +4231,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Tech Tonic podcast" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4390,7 +4390,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Sexual misconduct allegations" className="o-teaser__tag" data-trackable="teaser-tag" href="#" @@ -4503,7 +4503,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with video data className="o-teaser__meta-tag" > <a - aria-label="Category: {displayLink.prefLabel}" + aria-label="Category: Global Trade" className="o-teaser__tag" data-trackable="teaser-tag" href="#" From b77a372ea03f8bb416f6fb1d0157e77a7e0e0a2c Mon Sep 17 00:00:00 2001 From: Asuka Ochi <asuka.ochi@ft.com> Date: Fri, 30 Aug 2019 16:56:30 +0100 Subject: [PATCH 228/760] Change wording for headings --- __tests__/__snapshots__/snapshots.test.js.snap | 4 ++-- components/x-podcast-launchers/src/PodcastLaunchers.jsx | 4 ++-- .../__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 74691c7b0..95af37686 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -562,7 +562,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas <h2 className="PodcastLaunchers_heading__1103H" > - Subscribe on a podcast app + Subscribe via your installed podcast app </h2> <ul className="PodcastLaunchers_podcastAppLinksWrapper__7W6dn" @@ -639,7 +639,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas <h2 className="PodcastLaunchers_heading__1103H" > - Can’t see your podcast app? + Don't have a listed podcast app installed? </h2> <form action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.jsx b/components/x-podcast-launchers/src/PodcastLaunchers.jsx index 7a27d33fd..8983f82da 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.jsx +++ b/components/x-podcast-launchers/src/PodcastLaunchers.jsx @@ -69,7 +69,7 @@ class PodcastLaunchers extends Component { return rssUrl && ( <div className={styles.container} data-trackable='podcast-launchers'> - <h2 className={styles.heading}>Subscribe on a podcast app</h2> + <h2 className={styles.heading}>Subscribe via your installed podcast app</h2> <ul className={styles.podcastAppLinksWrapper}> {generateAppLinks(rssUrl).map(({ name, url, trackingId }) => ( <li key={name}> @@ -97,7 +97,7 @@ class PodcastLaunchers extends Component { </li> </ul> - <h2 className={styles.heading}>Can’t see your podcast app?</h2> + <h2 className={styles.heading}>Don't have a listed podcast app installed?</h2> {followButton(conceptId, conceptName, csrfToken, isFollowed)} </div> ) diff --git a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap index e357bd1f3..cf78e0115 100644 --- a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap +++ b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap @@ -7,7 +7,7 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = data-trackable="podcast-launchers" > <h2> - Subscribe on a podcast app + Subscribe via your installed podcast app </h2> <ul> <li> @@ -80,7 +80,7 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = </li> </ul> <h2> - Can’t see your podcast app? + Don't have a listed podcast app installed? </h2> <form action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" From 9d108cd73badaa9641beeac0c44acf21cbe09c7d Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Wed, 11 Sep 2019 14:49:58 +0100 Subject: [PATCH 229/760] Remove check for "root" data property from x-handlebars as it may be used safely --- packages/x-handlebars/index.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/x-handlebars/index.js b/packages/x-handlebars/index.js index 867580b0c..61d1070b5 100644 --- a/packages/x-handlebars/index.js +++ b/packages/x-handlebars/index.js @@ -43,15 +43,9 @@ module.exports = (userOptions = {}) => { throw TypeError(`The included component (${hash.component} from ${hash.local || hash.package}) is not a function, it is of type "${type}"`); } - // "this" is the current Handlebars context. don't merge it in if it's the root context - const props = Object.assign( - {}, - this === data.root ? {} : this, - mixins, - hash - ); - - // if this key is defined they've passed the root context in themselves, which is naughty + const props = Object.assign({}, this, mixins, hash); + + // Don't allow implementors to pass in the root context when using Express as the "locals" object may include sensitive data. if (props.hasOwnProperty('_locals')) { throw new Error(`The root handlebars context shouldn't be passed to a component, as it may contain sensitive data.`); } From f40f868b04490af38172d21d189e42ff6fe86463 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Wed, 11 Sep 2019 15:05:09 +0100 Subject: [PATCH 230/760] Remove unused data argument from x-handlebars helper function --- packages/x-handlebars/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-handlebars/index.js b/packages/x-handlebars/index.js index 61d1070b5..a54c046a8 100644 --- a/packages/x-handlebars/index.js +++ b/packages/x-handlebars/index.js @@ -11,7 +11,7 @@ module.exports = (userOptions = {}) => { const options = Object.assign({}, defaults, userOptions); // Return a regular function expression so that the template context may be shared (this) - return function({ hash, data }) { + return function({ hash }) { let moduleId; if (hash.hasOwnProperty('package')) { From 2d006cf38da47695b9b0c7e5053eacd8755bb49f Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Wed, 11 Sep 2019 15:06:06 +0100 Subject: [PATCH 231/760] Add comment with link to express render method implementation to x-handlebars helper function --- packages/x-handlebars/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/x-handlebars/index.js b/packages/x-handlebars/index.js index a54c046a8..3959605fa 100644 --- a/packages/x-handlebars/index.js +++ b/packages/x-handlebars/index.js @@ -46,6 +46,7 @@ module.exports = (userOptions = {}) => { const props = Object.assign({}, this, mixins, hash); // Don't allow implementors to pass in the root context when using Express as the "locals" object may include sensitive data. + // <https://github.com/expressjs/express/blob/0a48e18056865364b2461b2ece7ccb2d1075d3c9/lib/response.js#L1002-L1003> if (props.hasOwnProperty('_locals')) { throw new Error(`The root handlebars context shouldn't be passed to a component, as it may contain sensitive data.`); } From a069340cd6111fea50048bc237ad7ca305979e3e Mon Sep 17 00:00:00 2001 From: Maggie Allen <maggie.allen@ft.com> Date: Wed, 11 Sep 2019 15:34:58 +0100 Subject: [PATCH 232/760] Update the aria label on video teasers to be consistent with n-teaser --- .../__snapshots__/snapshots.test.js.snap | 72 +++++++++++++++++++ components/x-teaser/src/Title.jsx | 1 + 2 files changed, 73 insertions(+) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 9cb86f7c2..a12a104d9 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -28,6 +28,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -92,6 +93,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -156,6 +158,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -225,6 +228,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -294,6 +298,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -360,6 +365,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -424,6 +430,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -493,6 +500,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = ` className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -557,6 +565,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with article data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -608,6 +617,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage da className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -659,6 +669,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -715,6 +726,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with packageItem data className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -771,6 +783,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with podcast data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -824,6 +837,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with promoted data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -875,6 +889,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with topStory data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -931,6 +946,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with video data 1`] = className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -982,6 +998,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1046,6 +1063,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1110,6 +1128,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1179,6 +1198,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1248,6 +1268,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -1314,6 +1335,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1378,6 +1400,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1447,6 +1470,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] = className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1511,6 +1535,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with article data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1550,6 +1575,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage dat className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1589,6 +1615,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1633,6 +1660,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with packageItem data 1 className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1677,6 +1705,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with podcast data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -1718,6 +1747,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with promoted data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1757,6 +1787,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with topStory data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1855,6 +1886,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1894,6 +1926,7 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1970,6 +2003,7 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2046,6 +2080,7 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2127,6 +2162,7 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2208,6 +2244,7 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -2286,6 +2323,7 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2362,6 +2400,7 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2443,6 +2482,7 @@ exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = ` className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2519,6 +2559,7 @@ exports[`x-teaser / snapshots renders a Small teaser with article data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2558,6 +2599,7 @@ exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2597,6 +2639,7 @@ exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2641,6 +2684,7 @@ exports[`x-teaser / snapshots renders a Small teaser with packageItem data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2685,6 +2729,7 @@ exports[`x-teaser / snapshots renders a Small teaser with podcast data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -2726,6 +2771,7 @@ exports[`x-teaser / snapshots renders a Small teaser with promoted data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2765,6 +2811,7 @@ exports[`x-teaser / snapshots renders a Small teaser with topStory data 1`] = ` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2809,6 +2856,7 @@ exports[`x-teaser / snapshots renders a Small teaser with video data 1`] = ` className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2848,6 +2896,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2924,6 +2973,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3000,6 +3050,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3081,6 +3132,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3162,6 +3214,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -3240,6 +3293,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3316,6 +3370,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`] className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3397,6 +3452,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] = className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3473,6 +3529,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with article data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3524,6 +3581,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3575,6 +3633,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3631,6 +3690,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with packageItem data 1` className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3687,6 +3747,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with podcast data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -3740,6 +3801,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with promoted data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3791,6 +3853,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with topStory data 1`] = className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3884,6 +3947,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with video data 1`] = ` className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3935,6 +3999,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4011,6 +4076,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4087,6 +4153,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4168,6 +4235,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4249,6 +4317,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -4327,6 +4396,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4403,6 +4473,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d className="o-teaser__heading" > <a + aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4521,6 +4592,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with video data className="o-teaser__heading" > <a + aria-label="Watch video FT View: Donald Trump, man of steel" className="js-teaser-heading-link" data-trackable="heading-link" href="#" diff --git a/components/x-teaser/src/Title.jsx b/components/x-teaser/src/Title.jsx index a56880c86..09a0373ce 100644 --- a/components/x-teaser/src/Title.jsx +++ b/components/x-teaser/src/Title.jsx @@ -12,6 +12,7 @@ export default ({ title, altTitle, headlineTesting, relativeUrl, url, indicators <Link {...props} url={displayUrl} attrs={{ 'data-trackable': 'heading-link', className: 'js-teaser-heading-link', + 'aria-label': props.type === 'video' ? `Watch video ${displayTitle}` : null }}> {displayTitle} </Link> From c2ae1a4044ebf2cd182f17f8e832718edc8eefd6 Mon Sep 17 00:00:00 2001 From: Maggie Allen <maggie.allen@ft.com> Date: Wed, 11 Sep 2019 16:41:24 +0100 Subject: [PATCH 233/760] Update the aria label on podcast teasers --- .../__snapshots__/snapshots.test.js.snap | 72 +++---------------- components/x-teaser/src/Title.jsx | 8 ++- 2 files changed, 16 insertions(+), 64 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index a12a104d9..53af89b8d 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -28,7 +28,6 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -93,7 +92,6 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -158,7 +156,6 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -228,7 +225,6 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -298,7 +294,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -365,7 +361,6 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -430,7 +425,6 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -565,7 +559,6 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with article data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -617,7 +610,6 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage da className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -669,7 +661,6 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -726,7 +717,6 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with packageItem data className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -783,7 +773,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with podcast data 1`] className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -837,7 +827,6 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with promoted data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -889,7 +878,6 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with topStory data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -998,7 +986,6 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1063,7 +1050,6 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1128,7 +1114,6 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1198,7 +1183,6 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1268,7 +1252,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`] className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -1335,7 +1319,6 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1400,7 +1383,6 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1535,7 +1517,6 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with article data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1575,7 +1556,6 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage dat className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1615,7 +1595,6 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1660,7 +1639,6 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with packageItem data 1 className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1705,7 +1683,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with podcast data 1`] = className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -1747,7 +1725,6 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with promoted data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1787,7 +1764,6 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with topStory data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -1926,7 +1902,6 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2003,7 +1978,6 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2080,7 +2054,6 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2162,7 +2135,6 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2244,7 +2216,7 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -2323,7 +2295,6 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2400,7 +2371,6 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2559,7 +2529,6 @@ exports[`x-teaser / snapshots renders a Small teaser with article data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2599,7 +2568,6 @@ exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2639,7 +2607,6 @@ exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2684,7 +2651,6 @@ exports[`x-teaser / snapshots renders a Small teaser with packageItem data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2729,7 +2695,7 @@ exports[`x-teaser / snapshots renders a Small teaser with podcast data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -2771,7 +2737,6 @@ exports[`x-teaser / snapshots renders a Small teaser with promoted data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2811,7 +2776,6 @@ exports[`x-teaser / snapshots renders a Small teaser with topStory data 1`] = ` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2896,7 +2860,6 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -2973,7 +2936,6 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3050,7 +3012,6 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3132,7 +3093,6 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3214,7 +3174,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`] className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -3293,7 +3253,6 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3370,7 +3329,6 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`] className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3529,7 +3487,6 @@ exports[`x-teaser / snapshots renders a TopStory teaser with article data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3581,7 +3538,6 @@ exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3633,7 +3589,6 @@ exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3690,7 +3645,6 @@ exports[`x-teaser / snapshots renders a TopStory teaser with packageItem data 1` className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3747,7 +3701,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with podcast data 1`] = className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -3801,7 +3755,6 @@ exports[`x-teaser / snapshots renders a TopStory teaser with promoted data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3853,7 +3806,6 @@ exports[`x-teaser / snapshots renders a TopStory teaser with topStory data 1`] = className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -3999,7 +3951,6 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4076,7 +4027,6 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4153,7 +4103,6 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4235,7 +4184,6 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4317,7 +4265,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da className="o-teaser__heading" > <a - aria-label={null} + aria-label="Listen to podcast Who sets the internet standards?" className="js-teaser-heading-link" data-trackable="heading-link" href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" @@ -4396,7 +4344,6 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" @@ -4473,7 +4420,6 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d className="o-teaser__heading" > <a - aria-label={null} className="js-teaser-heading-link" data-trackable="heading-link" href="#" diff --git a/components/x-teaser/src/Title.jsx b/components/x-teaser/src/Title.jsx index 09a0373ce..e693e1cf3 100644 --- a/components/x-teaser/src/Title.jsx +++ b/components/x-teaser/src/Title.jsx @@ -6,13 +6,19 @@ export default ({ title, altTitle, headlineTesting, relativeUrl, url, indicators const displayUrl = relativeUrl || url; // o-labels--premium left for backwards compatibility for o-labels v3 const premiumClass = 'o-labels o-labels--premium o-labels--content-premium'; + let ariaLabel; + if(props.type === 'video') { + ariaLabel = `Watch video ${displayTitle}` + } else if (props.type === 'audio') { + ariaLabel = `Listen to podcast ${displayTitle}` + } return ( <div className="o-teaser__heading"> <Link {...props} url={displayUrl} attrs={{ 'data-trackable': 'heading-link', className: 'js-teaser-heading-link', - 'aria-label': props.type === 'video' ? `Watch video ${displayTitle}` : null + 'aria-label': ariaLabel }}> {displayTitle} </Link> From 1e6f28353ae2a6c39b302ad99aa178c940b3912e Mon Sep 17 00:00:00 2001 From: Asuka Ochi <asuka.ochi@ft.com> Date: Wed, 11 Sep 2019 17:50:14 +0100 Subject: [PATCH 234/760] Modify the section for when users can't find their podcast app --- .../__snapshots__/snapshots.test.js.snap | 73 +++++++++++-------- .../src/PodcastLaunchers.jsx | 13 +++- .../src/PodcastLaunchers.scss | 29 +++++++- .../PodcastLaunchers.test.jsx.snap | 53 ++++++++------ 4 files changed, 109 insertions(+), 59 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 95af37686..5e0cd32f6 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -560,7 +560,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas data-trackable="podcast-launchers" > <h2 - className="PodcastLaunchers_heading__1103H" + className="PodcastLaunchers_headingChooseApp__19hr-" > Subscribe via your installed podcast app </h2> @@ -636,39 +636,48 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas </div> </li> </ul> - <h2 - className="PodcastLaunchers_heading__1103H" - > - Don't have a listed podcast app installed? - </h2> - <form - action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" - data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" - method="GET" - onSubmit={[Function]} + <div + className="podcast-launchers__no-app-wrapper PodcastLaunchers_noAppWrapper__3AbOY" > - <input - data-myft-csrf-token={true} - name="token" - type="hidden" - value="token" - /> - <button - aria-label="Add Rachman Review to myFT" - aria-pressed="false" - className="main_button__3Mk67" - dangerouslySetInnerHTML={ - Object { - "__html": "Add to myFT", - } - } + <h2 + className="PodcastLaunchers_headingNoApp__3uLqT" + > + Can’t see your podcast app? + </h2> + <p + className="PodcastLaunchers_textNoApp__lmVJ1" + > + Get updates for new episodes + </p> + <form + action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" - data-trackable="follow" - data-trackable-context-messaging={null} - title="Add Rachman Review to myFT" - type="submit" - /> - </form> + method="GET" + onSubmit={[Function]} + > + <input + data-myft-csrf-token={true} + name="token" + type="hidden" + value="token" + /> + <button + aria-label="Add Rachman Review to myFT" + aria-pressed="false" + className="main_button__3Mk67" + dangerouslySetInnerHTML={ + Object { + "__html": "Add to myFT", + } + } + data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" + data-trackable="follow" + data-trackable-context-messaging={null} + title="Add Rachman Review to myFT" + type="submit" + /> + </form> + </div> </div> `; diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.jsx b/components/x-podcast-launchers/src/PodcastLaunchers.jsx index 8983f82da..95ea8c4a9 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.jsx +++ b/components/x-podcast-launchers/src/PodcastLaunchers.jsx @@ -32,6 +32,10 @@ const rssUrlCopyButtonWrapperStyles = [ styles.rssUrlCopyButton ].join(' '); +const noAppWrapperStyles = [ + 'podcast-launchers__no-app-wrapper', + styles.noAppWrapper +].join(' '); function defaultFollowButtonRender (conceptId, conceptName, csrfToken, isFollowed) { return ( @@ -69,7 +73,7 @@ class PodcastLaunchers extends Component { return rssUrl && ( <div className={styles.container} data-trackable='podcast-launchers'> - <h2 className={styles.heading}>Subscribe via your installed podcast app</h2> + <h2 className={styles.headingChooseApp}>Subscribe via your installed podcast app</h2> <ul className={styles.podcastAppLinksWrapper}> {generateAppLinks(rssUrl).map(({ name, url, trackingId }) => ( <li key={name}> @@ -97,8 +101,11 @@ class PodcastLaunchers extends Component { </li> </ul> - <h2 className={styles.heading}>Don't have a listed podcast app installed?</h2> - {followButton(conceptId, conceptName, csrfToken, isFollowed)} + <div className={noAppWrapperStyles}> + <h2 className={styles.headingNoApp}>Can’t see your podcast app?</h2> + <p className={styles.textNoApp}>Get updates for new episodes</p> + {followButton(conceptId, conceptName, csrfToken, isFollowed)} + </div> </div> ) } diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.scss b/components/x-podcast-launchers/src/PodcastLaunchers.scss index d46bb5be0..2afc1ae2a 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.scss +++ b/components/x-podcast-launchers/src/PodcastLaunchers.scss @@ -9,11 +9,38 @@ $o-typography-is-silent: true; width: 100%; } -.heading { +.headingChooseApp { @include oTypographySansBold($scale: 1); margin: 0 0 12px; } +.headingNoApp { + @include oTypographySansBold($scale: 0); + grid-area: heading; + margin: 0; +} + +.textNoApp { + @include oTypographySans($scale: 0); + grid-area: text; + margin: 0; +} + +.noAppWrapper { + display: grid; + grid-template: + 'heading button' + 'text button'; + grid-template-columns: max-content auto; + grid-template-rows: auto auto; + align-items: center; + + form { + grid-area: button; + justify-self: end; + } +} + .podcastAppLinksWrapper { list-style: none; padding: 0; diff --git a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap index cf78e0115..aec9bc755 100644 --- a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap +++ b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap @@ -79,30 +79,37 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = </div> </li> </ul> - <h2> - Don't have a listed podcast app installed? - </h2> - <form - action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" - data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" - method="GET" - onSubmit={[Function]} + <div + className="podcast-launchers__no-app-wrapper " > - <button - aria-label="Add undefined to myFT" - aria-pressed="false" - className="main_button__3Mk67" - dangerouslySetInnerHTML={ - Object { - "__html": "Add to myFT", - } - } + <h2> + Can’t see your podcast app? + </h2> + <p> + Get updates for new episodes + </p> + <form + action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" - data-trackable="follow" - data-trackable-context-messaging={null} - title="Add undefined to myFT" - type="submit" - /> - </form> + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Add undefined to myFT" + aria-pressed="false" + className="main_button__3Mk67" + dangerouslySetInnerHTML={ + Object { + "__html": "Add to myFT", + } + } + data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" + data-trackable="follow" + data-trackable-context-messaging={null} + title="Add undefined to myFT" + type="submit" + /> + </form> + </div> </div> `; From 5daff5663c30bc8f9733768a0487caa270e3e70c Mon Sep 17 00:00:00 2001 From: Asuka Ochi <asuka.ochi@ft.com> Date: Wed, 11 Sep 2019 18:03:01 +0100 Subject: [PATCH 235/760] Update readme screenshot --- components/x-podcast-launchers/readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/x-podcast-launchers/readme.md b/components/x-podcast-launchers/readme.md index 8f5ebaceb..71d038427 100644 --- a/components/x-podcast-launchers/readme.md +++ b/components/x-podcast-launchers/readme.md @@ -6,7 +6,8 @@ No elements are returned when the `conceptId` does not map to a known podcast se This component also renders a myFT follow button (x-follow-button) for the conceptId provided. This is acts as an onsite way to follow the series should the user's podcast app not be listed. -![screenshot of x-podcast-launchers](https://user-images.githubusercontent.com/21194161/63341610-86996080-c341-11e9-8a21-04da9c8bb6cc.png) +![screenshot of x-podcast-launchers](https://user-images.githubusercontent.com/21194161/64718501-3d5eab80-d4be-11e9-9a63-9b37ab1d8069.png) + ## Installation From 2ef7fecd9b07ae89c65c628fd104cba45c57aba3 Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Thu, 12 Sep 2019 12:14:01 +0100 Subject: [PATCH 236/760] Bring changelog up to speed with beta 16 and 17 --- changelog.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/changelog.md b/changelog.md index 31e806e64..556dd2978 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,15 @@ ## v1 +### v1.0.0-beta.17 + +- Aria label tweaks for video teasers (#397) +- Layout and content tweaks for podcast launchers (#395) + +### v1.0.0-beta.16 + +- Adds 'Category:' aria labels to teasers (#394) + ### v1.0.0-beta.15 - Adds x-podcast-launchers (#378)(#392) From 2ae04d4bb871eaebf15d2664a3b11852e02c54cc Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Thu, 12 Sep 2019 14:18:33 +0100 Subject: [PATCH 237/760] Update changelog.md --- changelog.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 556dd2978..b5f336473 100644 --- a/changelog.md +++ b/changelog.md @@ -2,14 +2,18 @@ ## v1 +### v1.0.0-beta.18 + +- Removed `root` context check from x-handlebars (#396) + ### v1.0.0-beta.17 -- Aria label tweaks for video teasers (#397) +- Aria label tweaks for video x-teasers (#397) - Layout and content tweaks for podcast launchers (#395) ### v1.0.0-beta.16 -- Adds 'Category:' aria labels to teasers (#394) +- Adds 'Category:' aria labels to x-teasers (#394) ### v1.0.0-beta.15 From 42b78b80b096c0ce6db916d93f3be3a0621eebe8 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Mon, 23 Sep 2019 09:17:49 +0100 Subject: [PATCH 238/760] Test if context prop is truthy before accessing properties --- components/x-teaser/src/MetaLink.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/src/MetaLink.jsx b/components/x-teaser/src/MetaLink.jsx index dfee3893c..259de7119 100644 --- a/components/x-teaser/src/MetaLink.jsx +++ b/components/x-teaser/src/MetaLink.jsx @@ -1,11 +1,11 @@ import { h } from '@financial-times/x-engine'; const sameId = (context = {}, id) => { - return id && context.parentId && id === context.parentId; + return id && context && context.parentId && id === context.parentId; }; const sameLabel = (context = {}, label) => { - return label && context.parentLabel && label === context.parentLabel; + return label && context && context.parentLabel && label === context.parentLabel; }; export default ({ metaPrefixText, metaLink, metaAltLink, metaSuffixText, context }) => { From 3f032fc46586de26214f76d76ae8deb2f7472107 Mon Sep 17 00:00:00 2001 From: Maggie Allen <maggie.allen@ft.com> Date: Thu, 3 Oct 2019 11:48:05 +0100 Subject: [PATCH 239/760] update related links url property to relativeUrl --- components/x-teaser/__fixtures__/top-story.json | 6 +++--- components/x-teaser/src/RelatedLinks.jsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/x-teaser/__fixtures__/top-story.json b/components/x-teaser/__fixtures__/top-story.json index 9b117bb93..e60192602 100644 --- a/components/x-teaser/__fixtures__/top-story.json +++ b/components/x-teaser/__fixtures__/top-story.json @@ -26,19 +26,19 @@ "relatedLinks": [ { "id": "", - "url": "#", + "relativeUrl": "#", "type": "article", "title": "Removing the fig leaf of charity" }, { "id": "", - "url": "#", + "relativeUrl": "#", "type": "article", "title": "A dinner that demeaned both women and men" }, { "id": "", - "url": "#", + "relativeUrl": "#", "type": "video", "title": "PM speaks out after Presidents Club dinner" } diff --git a/components/x-teaser/src/RelatedLinks.jsx b/components/x-teaser/src/RelatedLinks.jsx index 1facf8deb..8d15b61bb 100644 --- a/components/x-teaser/src/RelatedLinks.jsx +++ b/components/x-teaser/src/RelatedLinks.jsx @@ -1,11 +1,11 @@ import { h } from '@financial-times/x-engine'; -const renderLink = ({ id, url, type, title }, i) => ( +const renderLink = ({ id, relativeUrl, type, title }, i) => ( <li key={`related-${i}`} data-content-id={id} className={`o-teaser__related-item o-teaser__related-item--${type}`}> - <a data-trackable="related" href={url}> + <a data-trackable="related" href={relativeUrl}> {title} </a> </li> From 90ece033ede4c12fa3e4bbbdcc18f76b6d4ea627 Mon Sep 17 00:00:00 2001 From: notlee <lee.moody@ft.com> Date: Mon, 7 Oct 2019 14:59:02 +0100 Subject: [PATCH 240/760] Correct installation instructions to include a build step. --- docs/get-started/installation.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/get-started/installation.md b/docs/get-started/installation.md index ebf1a332e..053bd6c2d 100644 --- a/docs/get-started/installation.md +++ b/docs/get-started/installation.md @@ -29,14 +29,15 @@ To aid the development of interactive components with Storybook it is recommende cd x-dash ``` -2. Install all of the project dependendencies (this may take a few minutes if you are running this for the first time): +2. Install all of the project dependencies (this may take a few minutes if you are running this for the first time): ```bash make install ``` -3. Start Storybook to view the current set of x-dash components: +3. Build the current set of x-dash components and start Storybook to view: ```bash + make build npm run start-storybook ``` From 35abe61b1c3ea556ed51a0961037a321ca67b614 Mon Sep 17 00:00:00 2001 From: bren brightwell <bren@153.io> Date: Mon, 7 Oct 2019 16:25:49 +0100 Subject: [PATCH 241/760] Create CODEOWNERS --- CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..9a2401706 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +# See https://help.github.com/articles/about-codeowners/ for more information about this file. + +* matt.hinchliffe@ft.com bren.brightwell@ft.com From 2f08149388ac652844dbd813238386e1d1c4aa27 Mon Sep 17 00:00:00 2001 From: Renovate Bot <bot@renovateapp.com> Date: Mon, 7 Oct 2019 16:01:30 +0000 Subject: [PATCH 242/760] Update Node.js to v8.16 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 710bd5cfb..93d2d57c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ references: container_config_node8: &container_config_node8 working_directory: ~/project/build docker: - - image: circleci/node:8.15 + - image: circleci/node:8.16 workspace_root: &workspace_root ~/project From 0a0057204b61e0d6296d88a788a7b9dad2157624 Mon Sep 17 00:00:00 2001 From: Renovate Bot <bot@renovateapp.com> Date: Thu, 10 Oct 2019 16:41:51 +0000 Subject: [PATCH 243/760] Update dependency log-symbols to v3 --- packages/x-rollup/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-rollup/package.json b/packages/x-rollup/package.json index 97550a355..a25ddbfea 100644 --- a/packages/x-rollup/package.json +++ b/packages/x-rollup/package.json @@ -15,7 +15,7 @@ "@babel/plugin-external-helpers": "^7.2.0", "@financial-times/x-babel-config": "file:../x-babel-config", "chalk": "^2.4.1", - "log-symbols": "^2.2.0", + "log-symbols": "^3.0.0", "rollup": "^0.63.0", "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "^9.1.3", From c6d11862c000383b4cd390c4dee9325a82af3dbb Mon Sep 17 00:00:00 2001 From: Renovate Bot <bot@renovateapp.com> Date: Thu, 10 Oct 2019 16:42:17 +0000 Subject: [PATCH 244/760] Update dependency seedrandom to v3 --- packages/x-logo/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-logo/package.json b/packages/x-logo/package.json index df29b9eaa..0af98f157 100644 --- a/packages/x-logo/package.json +++ b/packages/x-logo/package.json @@ -12,6 +12,6 @@ "hsluv": "^0.0.3", "point-in-polygon": "^1.0.1", "poisson-disk-sampling": "^1.0.2", - "seedrandom": "^2.4.3" + "seedrandom": "^3.0.0" } } From 761f763e12636dcf79b1f11a34987059a6d389d8 Mon Sep 17 00:00:00 2001 From: Renovate Bot <bot@renovateapp.com> Date: Fri, 11 Oct 2019 07:26:36 +0000 Subject: [PATCH 245/760] Update dependency preact to v10 --- tools/x-ssr-demo/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/x-ssr-demo/package.json b/tools/x-ssr-demo/package.json index 1dca25188..f1e85d7ac 100644 --- a/tools/x-ssr-demo/package.json +++ b/tools/x-ssr-demo/package.json @@ -22,7 +22,7 @@ "babel-loader": "^8.0.5", "express": "^4.16.3", "hyperons": "^0.5.0", - "preact": "^8.2.9", + "preact": "^10.0.0", "webpack": "^4.8.1", "webpack-dev-middleware": "^3.1.3" }, From 2db95e3c487c27f82bcc056341269bc1959a0c96 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt@maketea.co.uk> Date: Fri, 11 Oct 2019 08:51:48 +0100 Subject: [PATCH 246/760] Update x-rollup dependencies to use rollup v0.6.x to v1.x and latest versions of rollup plugins --- packages/x-rollup/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/x-rollup/package.json b/packages/x-rollup/package.json index a25ddbfea..c0f796da1 100644 --- a/packages/x-rollup/package.json +++ b/packages/x-rollup/package.json @@ -11,14 +11,14 @@ "author": "", "license": "ISC", "dependencies": { - "@babel/core": "^7.4.3", + "@babel/core": "^7.6.4", "@babel/plugin-external-helpers": "^7.2.0", "@financial-times/x-babel-config": "file:../x-babel-config", - "chalk": "^2.4.1", + "chalk": "^2.4.2", "log-symbols": "^3.0.0", - "rollup": "^0.63.0", + "rollup": "^1.23.0", "rollup-plugin-babel": "^4.3.2", - "rollup-plugin-commonjs": "^9.1.3", - "rollup-plugin-postcss": "^1.6.2" + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-postcss": "^2.0.2" } } From acbb34e1a12e03f1bd7192eb2b470029047e6179 Mon Sep 17 00:00:00 2001 From: Maggie Allen <maggie.allen@ft.com> Date: Fri, 11 Oct 2019 12:03:37 +0100 Subject: [PATCH 247/760] related content links can use url or relatedUrl as the href --- components/x-teaser/src/RelatedLinks.jsx | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/components/x-teaser/src/RelatedLinks.jsx b/components/x-teaser/src/RelatedLinks.jsx index 8d15b61bb..34ecff94d 100644 --- a/components/x-teaser/src/RelatedLinks.jsx +++ b/components/x-teaser/src/RelatedLinks.jsx @@ -1,15 +1,18 @@ import { h } from '@financial-times/x-engine'; -const renderLink = ({ id, relativeUrl, type, title }, i) => ( - <li - key={`related-${i}`} - data-content-id={id} - className={`o-teaser__related-item o-teaser__related-item--${type}`}> - <a data-trackable="related" href={relativeUrl}> - {title} - </a> - </li> -); +const renderLink = ({ id, type, title, url, relativeUrl }, i) => { + const displayUrl = relativeUrl || url; + return ( + <li + key={`related-${i}`} + data-content-id={id} + className={`o-teaser__related-item o-teaser__related-item--${type}`}> + <a data-trackable="related" href={displayUrl}> + {title} + </a> + </li> + ); +} export default ({ relatedLinks = [] }) => ( relatedLinks && relatedLinks.length ? ( From 8a87269743b125b509f324ba28ffff8d4ec2bc36 Mon Sep 17 00:00:00 2001 From: notlee <lee.moody@ft.com> Date: Tue, 15 Oct 2019 11:08:28 +0100 Subject: [PATCH 248/760] Remove markup not defined by o-teaser. [This o-teaser PR](https://github.com/Financial-Times/o-teaser/pull/138) by Olga introduces flexbox to the meta tag. It enforces no spacing from whitespace between tag children. When there is no whitespace it allows a tag, which is shorter than the width of the teaser, to wrap rather than overflow or break when alongside a prefix. But it would break x-dash implementations because of the extra wrapping elements. The app is already [using this new markup](https://github.com/Financial-Times/ft-app/blob/92c5799a00b3e82aa0aa13dc30d7b813cbd7f58d/lib/components/page/slots/weekend/styles.scss#L135) to override styles, which should be updated before this is released. I think this approach is better than absorbing the extra markup into o-teaser. --- .../__snapshots__/snapshots.test.js.snap | 1602 +++++++---------- components/x-teaser/src/Meta.jsx | 6 +- components/x-teaser/src/MetaLink.jsx | 2 +- components/x-teaser/src/Promoted.jsx | 2 +- 4 files changed, 660 insertions(+), 952 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 53af89b8d..a89d0139c 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -11,18 +11,14 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -75,18 +71,14 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -139,18 +131,14 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -203,23 +191,19 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -272,23 +256,19 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -342,20 +322,16 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -408,18 +384,14 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -472,23 +444,19 @@ exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__heading" @@ -542,18 +510,14 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with article data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -593,18 +557,14 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage da <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -644,18 +604,14 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -695,23 +651,19 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with packageItem data <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -751,23 +703,19 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with podcast data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -808,20 +756,16 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with promoted data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -861,18 +805,14 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with topStory data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -912,23 +852,19 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with video data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__heading" @@ -969,18 +905,14 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -1033,18 +965,14 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -1097,18 +1025,14 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -1161,23 +1085,19 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -1230,23 +1150,19 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -1300,20 +1216,16 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -1366,18 +1278,14 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -1430,23 +1338,19 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__heading" @@ -1500,18 +1404,14 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with article data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -1539,18 +1439,14 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage dat <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -1578,18 +1474,14 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -1617,23 +1509,19 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with packageItem data 1 <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -1661,23 +1549,19 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with podcast data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -1706,20 +1590,16 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with promoted data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -1747,18 +1627,14 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with topStory data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -1786,23 +1662,19 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__video" @@ -1885,18 +1757,14 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -1961,18 +1829,14 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -2037,18 +1901,14 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -2113,23 +1973,19 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -2194,23 +2050,19 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -2276,20 +2128,16 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -2354,18 +2202,14 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -2430,23 +2274,19 @@ exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__heading" @@ -2512,18 +2352,14 @@ exports[`x-teaser / snapshots renders a Small teaser with article data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -2551,18 +2387,14 @@ exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -2590,18 +2422,14 @@ exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -2629,23 +2457,19 @@ exports[`x-teaser / snapshots renders a Small teaser with packageItem data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -2673,23 +2497,19 @@ exports[`x-teaser / snapshots renders a Small teaser with podcast data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -2718,20 +2538,16 @@ exports[`x-teaser / snapshots renders a Small teaser with promoted data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -2759,18 +2575,14 @@ exports[`x-teaser / snapshots renders a Small teaser with topStory data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -2798,23 +2610,19 @@ exports[`x-teaser / snapshots renders a Small teaser with video data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__heading" @@ -2843,18 +2651,14 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -2919,18 +2723,14 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -2995,18 +2795,14 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -3071,23 +2867,19 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -3152,23 +2944,19 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -3234,20 +3022,16 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -3312,18 +3096,14 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`] <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -3388,23 +3168,19 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__heading" @@ -3470,18 +3246,14 @@ exports[`x-teaser / snapshots renders a TopStory teaser with article data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -3521,18 +3293,14 @@ exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -3572,18 +3340,14 @@ exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -3623,23 +3387,19 @@ exports[`x-teaser / snapshots renders a TopStory teaser with packageItem data 1` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -3679,23 +3439,19 @@ exports[`x-teaser / snapshots renders a TopStory teaser with podcast data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -3736,20 +3492,16 @@ exports[`x-teaser / snapshots renders a TopStory teaser with promoted data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -3789,18 +3541,14 @@ exports[`x-teaser / snapshots renders a TopStory teaser with topStory data 1`] = <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -3877,23 +3625,19 @@ exports[`x-teaser / snapshots renders a TopStory teaser with video data 1`] = ` <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__heading" @@ -3934,18 +3678,14 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -4010,18 +3750,14 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: FT Magazine" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: FT Magazine" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> + FT Magazine + </a> </div> <div className="o-teaser__heading" @@ -4086,18 +3822,14 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Gideon Rachman" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Gideon Rachman" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> + Gideon Rachman + </a> </div> <div className="o-teaser__heading" @@ -4162,23 +3894,19 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - aria-label="Category: Financial crisis: Are we safer now? " - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> + FT Series + </span> + <a + aria-label="Category: Financial crisis: Are we safer now? " + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> </div> <div className="o-teaser__heading" @@ -4243,23 +3971,19 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Tech Tonic podcast" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Tech Tonic podcast" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> </div> <div className="o-teaser__heading" @@ -4325,20 +4049,16 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d <div className="o-teaser__meta" > - <div - className="o-teaser__meta-promoted" + <span + className="o-teaser__promoted-prefix" > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> </div> <div className="o-teaser__heading" @@ -4403,18 +4123,14 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Sexual misconduct allegations" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> + Sexual misconduct allegations + </a> </div> <div className="o-teaser__heading" @@ -4516,23 +4232,19 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with video data <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global Trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" > - <a - aria-label="Category: Global Trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> </div> <div className="o-teaser__heading" diff --git a/components/x-teaser/src/Meta.jsx b/components/x-teaser/src/Meta.jsx index c63fd2a6e..9251b6ef3 100644 --- a/components/x-teaser/src/Meta.jsx +++ b/components/x-teaser/src/Meta.jsx @@ -5,9 +5,5 @@ import Promoted from './Promoted'; export default (props) => { const showPromoted = props.promotedPrefixText && props.promotedSuffixText; - return ( - <div className="o-teaser__meta"> - {showPromoted ? <Promoted {...props} /> : <MetaLink {...props} />} - </div> - ); + return showPromoted ? <Promoted {...props} /> : <MetaLink {...props} />; }; diff --git a/components/x-teaser/src/MetaLink.jsx b/components/x-teaser/src/MetaLink.jsx index 259de7119..d1ebe68bd 100644 --- a/components/x-teaser/src/MetaLink.jsx +++ b/components/x-teaser/src/MetaLink.jsx @@ -17,7 +17,7 @@ export default ({ metaPrefixText, metaLink, metaAltLink, metaSuffixText, context const displayLink = useAltLink ? metaAltLink : metaLink; return ( - <div className="o-teaser__meta-tag"> + <div className="o-teaser__meta"> {showPrefixText ? <span className="o-teaser__tag-prefix">{metaPrefixText}</span> : null} {displayLink ? ( <a diff --git a/components/x-teaser/src/Promoted.jsx b/components/x-teaser/src/Promoted.jsx index ca5ef997c..a7c4dd205 100644 --- a/components/x-teaser/src/Promoted.jsx +++ b/components/x-teaser/src/Promoted.jsx @@ -1,7 +1,7 @@ import { h } from '@financial-times/x-engine'; export default ({ promotedPrefixText, promotedSuffixText }) => ( - <div className="o-teaser__meta-promoted"> + <div className="o-teaser__meta"> <span className="o-teaser__promoted-prefix">{promotedPrefixText}</span> <span className="o-teaser__promoted-by">{` ${promotedSuffixText} `}</span> </div> From 0016c7eb3aff6a106f3e6bf5b638d9d2823e9481 Mon Sep 17 00:00:00 2001 From: Will Howard <willhowardgb@gmail.com> Date: Wed, 23 Oct 2019 09:30:45 +0100 Subject: [PATCH 249/760] Added dataUrl conditional to bypass constructing the URL if it's a self-contained image URL. --- components/x-teaser/src/Image.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 932abbc64..9fe2a20fa 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -26,9 +26,9 @@ const LazyImage = ({ src, lazyLoad }) => { return <img className={`o-teaser__image ${lazyClassName}`} data-src={src} alt="" />; }; -export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { +export default ({ dataUrl, relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { const displayUrl = relativeUrl || url; - const imageSrc = imageService(image.url, ImageSizes[imageSize]); + const imageSrc = dataUrl ? image.url : imageService(image.url, ImageSizes[imageSize]); const ImageComponent = imageLazyLoad ? LazyImage : NormalImage; return image ? ( From 8c97dde329271e68b496abae13b6c298421415d3 Mon Sep 17 00:00:00 2001 From: Will Howard <willhowardgb@gmail.com> Date: Wed, 23 Oct 2019 10:12:30 +0100 Subject: [PATCH 250/760] Renamed prop, updated documentation. --- components/x-teaser/readme.md | 1 + components/x-teaser/src/Image.jsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index 223c844f3..ec7f2317c 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -171,6 +171,7 @@ Property | Type | Notes `image` | [media](#media-props) | `imageSize` | String | XS, Small, Medium, Large, XL or XXL `imageLazyload` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. +`imageDataUrl` | Boolean | Signifies that the image URL is a self-contained data URL. [nimg]: https://github.com/Financial-Times/n-image/ diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 9fe2a20fa..a990a449c 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -26,9 +26,9 @@ const LazyImage = ({ src, lazyLoad }) => { return <img className={`o-teaser__image ${lazyClassName}`} data-src={src} alt="" />; }; -export default ({ dataUrl, relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { +export default ({ imageDataUrl, relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { const displayUrl = relativeUrl || url; - const imageSrc = dataUrl ? image.url : imageService(image.url, ImageSizes[imageSize]); + const imageSrc = imageDataUrl ? image.url : imageService(image.url, ImageSizes[imageSize]); const ImageComponent = imageLazyLoad ? LazyImage : NormalImage; return image ? ( From 2b9e4d545da1431adcea68146e7210374a188616 Mon Sep 17 00:00:00 2001 From: Will Howard <willhowardgb@gmail.com> Date: Wed, 23 Oct 2019 10:23:20 +0100 Subject: [PATCH 251/760] Fixed broken tabs/spaces. --- components/x-teaser/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index ec7f2317c..db0caf751 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -171,7 +171,7 @@ Property | Type | Notes `image` | [media](#media-props) | `imageSize` | String | XS, Small, Medium, Large, XL or XXL `imageLazyload` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. -`imageDataUrl` | Boolean | Signifies that the image URL is a self-contained data URL. +`imageDataUrl` | Boolean, String | Signifies that the image URL is a self-contained data URL. [nimg]: https://github.com/Financial-Times/n-image/ From 47e3abbc3bb3825cb26abe0a9aedce1bad6a8db6 Mon Sep 17 00:00:00 2001 From: notlee <lee.moody@ft.com> Date: Wed, 23 Oct 2019 14:52:30 +0100 Subject: [PATCH 252/760] Remove release guide misdirection (no commit to master allowed). --- docs/components/release-guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/components/release-guidelines.md b/docs/components/release-guidelines.md index d4e99c79a..68c73e58a 100644 --- a/docs/components/release-guidelines.md +++ b/docs/components/release-guidelines.md @@ -8,7 +8,7 @@ To develop your component create a new feature branch including your module name Because experimental modules will not be included in any stable releases we allow them to be published separately using a pre-1.0.0 version number. You are free to make as many prereleases as you need. To create a prerelease of your experimental module you must create a tag in the format `module-name-v0.x.x`, for example to release the tabs component you would create tag named `x-tabs-v0.0.1` for the latest commit in the `feature-x-tabs` branch. -You are encouraged to use an identifier to namespace your prereleases, e.g. `x-tags-v0.0.1-beta.1`, as this will also prevent Renovate from automatically creating a PR for updating applications using an earlier version of your component (this would be undesirable if your new component version contained breaking changes which cannot be expressed with semver). +You are encouraged to use an identifier to namespace your prereleases, e.g. `x-tags-v0.0.1-beta.1`, as this will also prevent Renovate from automatically creating a PR for updating applications using an earlier version of your component (this would be undesirable if your new component version contained breaking changes which cannot be expressed with semver). When your new module is considered stable raise a pull request against the current development branch. Your module will be released as part of the next major or minor version. @@ -25,7 +25,7 @@ All of our projects are versioned using [Semantic Versioning], you should famili 4. **Update any package files**. Add the new version to package files. This could include `package.json` or `bower.json` as examples. A quick way to check if you've got them all is by running: `git grep "current-version-number"` - 5. **Commit your changes**. Commit the changes to changelong, README, and package files. The commit message should be "Version x.x.x" (exact casing, and with no "v" preceeding the version number). This is the _only_ time you're allowed to commit directly to `master`. + 5. **Commit your changes**. Commit the changes to changelong, README, and package files. The commit message should be "Version x.x.x" (exact casing, and with no "v" preceeding the version number). 6. **Add a release**. Create a release using the GitHub UI (note there should be a "v" preceeding the version number). This will automatically kick off a new build and publish each package. From 74090c62774ca818d2f9ced416ffda0cb656f539 Mon Sep 17 00:00:00 2001 From: notlee <lee.moody@ft.com> Date: Wed, 23 Oct 2019 14:51:10 +0100 Subject: [PATCH 253/760] Version 1.0.0-beta.20 --- changelog.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/changelog.md b/changelog.md index b5f336473..de29053f6 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,14 @@ ## v1 +### v1.0.0-beta.20 + +- Remove x-teaser markup not defined within o-teaser (#406) + +### v1.0.0-beta.19 + +- Allows the `relatedLinks` x-teaser component to use either the `relatedUrl` or the `url` property if available (#405) + ### v1.0.0-beta.18 - Removed `root` context check from x-handlebars (#396) From 8ed75d800e0ed2daedd928cbba1b28d791182eb6 Mon Sep 17 00:00:00 2001 From: Lee Moody <notlee@users.noreply.github.com> Date: Wed, 23 Oct 2019 14:57:51 +0100 Subject: [PATCH 254/760] Update docs/components/release-guidelines.md Co-Authored-By: bren brightwell <bren@153.io> --- docs/components/release-guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/components/release-guidelines.md b/docs/components/release-guidelines.md index 68c73e58a..5345fa402 100644 --- a/docs/components/release-guidelines.md +++ b/docs/components/release-guidelines.md @@ -25,7 +25,7 @@ All of our projects are versioned using [Semantic Versioning], you should famili 4. **Update any package files**. Add the new version to package files. This could include `package.json` or `bower.json` as examples. A quick way to check if you've got them all is by running: `git grep "current-version-number"` - 5. **Commit your changes**. Commit the changes to changelong, README, and package files. The commit message should be "Version x.x.x" (exact casing, and with no "v" preceeding the version number). + 5. **Commit your changes**. Commit the changes to changelog, README, and package files. The commit message should be "Version x.x.x" (exact casing, and with no "v" preceeding the version number). 6. **Add a release**. Create a release using the GitHub UI (note there should be a "v" preceeding the version number). This will automatically kick off a new build and publish each package. From 6a8a4b499a3d0fc8c3f3b18eca95d4bb9be1b96b Mon Sep 17 00:00:00 2001 From: Will Howard <willhowardgb@gmail.com> Date: Thu, 24 Oct 2019 09:31:08 +0100 Subject: [PATCH 255/760] Updated to remove extra prop. --- components/x-teaser/readme.md | 3 +-- components/x-teaser/src/Image.jsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index db0caf751..96bbb3e0d 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -171,7 +171,6 @@ Property | Type | Notes `image` | [media](#media-props) | `imageSize` | String | XS, Small, Medium, Large, XL or XXL `imageLazyload` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. -`imageDataUrl` | Boolean, String | Signifies that the image URL is a self-contained data URL. [nimg]: https://github.com/Financial-Times/n-image/ @@ -237,7 +236,7 @@ Property | Type | Notes Property | Type | Notes ---------|--------|-------------- -`url` | String | Content UUID +`url` | String | Content UUID or, in the case of images, `data:` URL `width` | Number | `height` | Number | diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index a990a449c..7fd0865b3 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -28,7 +28,7 @@ const LazyImage = ({ src, lazyLoad }) => { export default ({ imageDataUrl, relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { const displayUrl = relativeUrl || url; - const imageSrc = imageDataUrl ? image.url : imageService(image.url, ImageSizes[imageSize]); + const imageSrc = image.url.startsWith('data:') ? image.url : imageService(image.url, ImageSizes[imageSize]); const ImageComponent = imageLazyLoad ? LazyImage : NormalImage; return image ? ( From e63035e4070b2eaee0c85aac5ec8c75fe708aa77 Mon Sep 17 00:00:00 2001 From: Will Howard <willhowardgb@gmail.com> Date: Thu, 24 Oct 2019 09:31:59 +0100 Subject: [PATCH 256/760] Linted. --- components/x-teaser/src/Image.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 7fd0865b3..4a0dddeac 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -26,7 +26,7 @@ const LazyImage = ({ src, lazyLoad }) => { return <img className={`o-teaser__image ${lazyClassName}`} data-src={src} alt="" />; }; -export default ({ imageDataUrl, relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { +export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { const displayUrl = relativeUrl || url; const imageSrc = image.url.startsWith('data:') ? image.url : imageService(image.url, ImageSizes[imageSize]); const ImageComponent = imageLazyLoad ? LazyImage : NormalImage; From 785e38554adb95eb45518ab1841e442eb1874b6e Mon Sep 17 00:00:00 2001 From: willhoward <43537633+willhoward@users.noreply.github.com> Date: Fri, 25 Oct 2019 09:34:05 +0100 Subject: [PATCH 257/760] Update changelog.md Updated changelog to include latest patch release --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index de29053f6..4e68dfcd7 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,11 @@ ## v1 + +### v1.0.0-beta.21 + +- Add detection for data URLs in x-teasers (#407) + ### v1.0.0-beta.20 - Remove x-teaser markup not defined within o-teaser (#406) From 5db4acbe6b2631254d7091eb831f1600893f839b Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Tue, 5 Nov 2019 13:59:43 +0000 Subject: [PATCH 258/760] =?UTF-8?q?Don=E2=80=99t=20try=20to=20insert=20cus?= =?UTF-8?q?tomSlotContent=20if=20we=20have=20no=20itemGroups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i.e. if there is no content, don’t insert the custom slot either. --- components/x-teaser-timeline/src/TeaserTimeline.jsx | 2 +- components/x-teaser-timeline/src/lib/transform.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index 562b22900..f2ba515a4 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -15,7 +15,7 @@ const TeaserTimeline = props => { } = props; const itemGroups = getItemGroups(props); - if (customSlotContent) { + if (itemGroups.length > 0 && customSlotContent) { const insertPosition = Math.min(customSlotPosition, items.length); const insert = getGroupAndIndex(itemGroups, insertPosition); const copyOfItems = [...itemGroups[insert.group].items]; diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index dd13d4433..1b70e866f 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -102,6 +102,6 @@ export const getGroupAndIndex = (groups, position) => { return { group: 0, - item: 0 + index: 0 }; }; From 0330e47d8a090e9bee0d6ec58a49473518ee050e Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Tue, 5 Nov 2019 16:56:34 +0000 Subject: [PATCH 259/760] Update snappies --- .../__snapshots__/snapshots.test.js.snap | 2546 +++++++++++++++ .../TeaserTimeline.test.jsx.snap | 2860 +++++++---------- 2 files changed, 3706 insertions(+), 1700 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 5e0cd32f6..5f0bc86e1 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -688,3 +688,2549 @@ exports[`@financial-times/x-styling-demo renders a default Styling x-styling-dem Click me! </button> `; + +exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-timeline 1`] = ` +<div> + <section + className="TeaserTimeline_itemGroup__2YuVE" + > + <h2 + className="TeaserTimeline_itemGroup__heading__3KrJD" + > + Latest News + </h2> + <ul + className="TeaserTimeline_itemGroup__items__3ZuL6" + > + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="65867e26-d203-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" + > + Brexit Briefing + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" + > + Is December looming as the new Brexit deadline? + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fc8a8d52e-d20a-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/65867e26-d203-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="65867e26-d203-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Is December looming as the new Brexit deadline? to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="65867e26-d203-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="93614586-d1f1-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" + > + May seeks to overcome deadlock after Brexit setback + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + UK and EU consider extending transition deal to defuse dispute over Irish border backstop + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F8ba50178-d216-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/93614586-d1f1-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="93614586-d1f1-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Saved to myFT" + aria-pressed={true} + className="ArticleSaveButton_button__2_wUr" + data-content-id="93614586-d1f1-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Saved + </button> + </form> + </div> + </li> + </ul> + </section> + <section + className="TeaserTimeline_itemGroup__2YuVE" + > + <h2 + className="TeaserTimeline_itemGroup__heading__3KrJD" + > + Earlier Today + </h2> + <ul + className="TeaserTimeline_itemGroup__items__3ZuL6" + > + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="d4e80114-d1ee-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" + > + Midsized UK businesses turn sour on Brexit + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Quarterly survey shows more companies now believe Brexit will damage their business than help them + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa3695666-d201-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="d4e80114-d1ee-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Midsized UK businesses turn sour on Brexit to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="d4e80114-d1ee-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + dangerouslySetInnerHTML={ + Object { + "__html": "Custom slot content", + } + } + /> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="6582b8ce-d175-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" + > + Barnier open to extending Brexit transition by a year + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F9be47d9e-d17a-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/6582b8ce-d175-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="6582b8ce-d175-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Barnier open to extending Brexit transition by a year to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="6582b8ce-d175-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image o-teaser--opinion js-teaser" + data-id="7ab52d68-d11a-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <span + className="o-teaser__tag-prefix" + > + Inside Business + </span> + <a + aria-label="Category: Sarah Gordon" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/sarah-gordon" + > + Sarah Gordon + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" + > + UK lets down business with lack of Brexit advice + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Governments should be more concerned about SMEs: if supply chains falter, so will economic growth + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F23529cb0-d140-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="7ab52d68-d11a-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save UK lets down business with lack of Brexit advice to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="7ab52d68-d11a-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="868cedae-d18a-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Global trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/global-trade" + > + Global trade + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" + > + Trump looks to start formal US-UK trade talks + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + White House makes London a priority ‘as soon as it is ready’ after Brexit + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Ff08f8340-d1a0-11e8-a9f2-7574db66bcd5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/868cedae-d18a-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="868cedae-d18a-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Trump looks to start formal US-UK trade talks to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="868cedae-d18a-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + </ul> + </section> + <section + className="TeaserTimeline_itemGroup__2YuVE" + > + <h2 + className="TeaserTimeline_itemGroup__heading__3KrJD" + > + Yesterday + </h2> + <ul + className="TeaserTimeline_itemGroup__items__3ZuL6" + > + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="c276c8a2-d159-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: House of Commons UK" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" + > + House of Commons UK + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" + > + Bercow faces mounting calls to resign + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fece474ca-d151-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/c276c8a2-d159-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="c276c8a2-d159-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Bercow faces mounting calls to resign to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="c276c8a2-d159-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image o-teaser--opinion js-teaser" + data-id="a1566658-d12e-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <span + className="o-teaser__tag-prefix" + > + The FT View + </span> + <a + aria-label="Category: The editorial board" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/ft-view" + > + The editorial board + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" + > + A culture shift to clear Westminster’s toxic air + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Speaker John Bercow should take responsibility — and go now + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F5319de7e-d12f-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/a1566658-d12e-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="a1566658-d12e-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save A culture shift to clear Westminster’s toxic air to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="a1566658-d12e-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="f9f69a2c-d141-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" + > + EU demands UK break Brexit impasse + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F62fd9e46-d14f-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="f9f69a2c-d141-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save EU demands UK break Brexit impasse to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="f9f69a2c-d141-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="eb6062c2-d13c-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" + > + Brexit Briefing + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" + > + Brexit, Scotland and the threat to constitutional unity + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fcb11f55e-d140-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="eb6062c2-d13c-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Brexit, Scotland and the threat to constitutional unity to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="eb6062c2-d13c-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="d7472b20-d148-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" + > + EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F383a4ea2-d14a-11e8-a9f2-7574db66bcd5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/d7472b20-d148-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="d7472b20-d148-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="d7472b20-d148-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="10ee3a20-d13b-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Wells Fargo" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" + > + Wells Fargo + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" + > + Wells Fargo applies for licence in France as part of Brexit strategy + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F31d2be9e-d13d-11e8-a9f2-7574db66bcd5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="10ee3a20-d13b-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Wells Fargo applies for licence in France as part of Brexit strategy to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="10ee3a20-d13b-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: UK welfare reform" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" + > + UK welfare reform + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" + > + Rollout of controversial UK welfare reform faces fresh delay + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Universal credit system not expected to be fully operational until December 2023 + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fd7517de4-d131-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Rollout of controversial UK welfare reform faces fresh delay to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser js-teaser" + data-id="1969887a-d11e-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" + > + Pound Sterling + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" + > + Pound rallies after upbeat wage growth reading + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Analysts remain cautious despite optimistic data + </a> + </p> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/1969887a-d11e-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="1969887a-d11e-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Pound rallies after upbeat wage growth reading to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="1969887a-d11e-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: UK politics & policy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world/uk/politics" + > + UK politics & policy + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" + > + Problem gambling shake-up set to be brought forward + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" + tabIndex={-1} + > + Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa6afae18-d069-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" + className="ArticleSaveButton_root__Utel0" + data-content-id="dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Problem gambling shake-up set to be brought forward to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Technology sector" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/technology" + > + Technology sector + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" + > + Crypto exchange Coinbase sets up Brexit contingency + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + US digital start-up to scale up EU operations with new Dublin branch + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F9a6adda6-d07a-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Crypto exchange Coinbase sets up Brexit contingency to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + </ul> + </section> + <section + className="TeaserTimeline_itemGroup__2YuVE" + > + <h2 + className="TeaserTimeline_itemGroup__heading__3KrJD" + > + October 15, 2018 + </h2> + <ul + className="TeaserTimeline_itemGroup__items__3ZuL6" + > + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="2e407a74-d06a-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: World" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world" + > + World + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" + > + EU gives UK 24 hour Brexit breathing space + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fac0fd098-d075-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/2e407a74-d06a-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="2e407a74-d06a-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save EU gives UK 24 hour Brexit breathing space to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="2e407a74-d06a-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser js-teaser" + data-id="434dc8e2-d09f-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" + > + EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="434dc8e2-d09f-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="434dc8e2-d09f-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="fadfb212-d091-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" + > + Barnier plan no solution to Irish border, says Foster + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fd0cbda02-cbb7-11e8-8d0b-a6539b949662?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/fadfb212-d091-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="fadfb212-d091-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Barnier plan no solution to Irish border, says Foster to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="fadfb212-d091-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="79939f94-c320-11e8-8d55-54197280d3f7" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <span + className="o-teaser__tag-prefix" + > + Explainer + </span> + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" + > + Pound Sterling + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/79939f94-c320-11e8-8d55-54197280d3f7" + > + Pound timeline: from the 1970s to Brexit crunch + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/79939f94-c320-11e8-8d55-54197280d3f7" + tabIndex={-1} + > + Sterling has experienced several periods of volatility in the past 48 years + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/79939f94-c320-11e8-8d55-54197280d3f7" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Faea311c6-c32d-11e8-95b1-d36dfef1b89a?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/79939f94-c320-11e8-8d55-54197280d3f7" + className="ArticleSaveButton_root__Utel0" + data-content-id="79939f94-c320-11e8-8d55-54197280d3f7" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Pound timeline: from the 1970s to Brexit crunch to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="79939f94-c320-11e8-8d55-54197280d3f7" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="bfffa642-d079-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" + > + Brexit talks could drag into December warns Ireland’s Varadkar + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + Taoiseach says he always believed a deal this month was unlikely + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Ff87f782a-d089-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/bfffa642-d079-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="bfffa642-d079-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Brexit talks could drag into December warns Ireland’s Varadkar to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="bfffa642-d079-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="82ae2756-d073-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" + > + Brexit Briefing + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" + > + Crisis or choreography over Brexit? + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" + tabIndex={-1} + > + While ministers haggle, company bosses press the button on expensive no-deal planning + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F31c1c612-d079-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/82ae2756-d073-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="82ae2756-d073-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Crisis or choreography over Brexit? to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="82ae2756-d073-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="a8181102-ce1e-11e8-9fe5-24ad351828ab" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Industrials" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/industrials" + > + Industrials + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" + > + UK shipyards to submit bids to build 5 warships + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" + tabIndex={-1} + > + MoD restarts competition despite industry’s concerns over budget and timing + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Ff1191270-ce41-11e8-8d0b-a6539b949662?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/a8181102-ce1e-11e8-9fe5-24ad351828ab" + className="ArticleSaveButton_root__Utel0" + data-content-id="a8181102-ce1e-11e8-9fe5-24ad351828ab" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save UK shipyards to submit bids to build 5 warships to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="a8181102-ce1e-11e8-9fe5-24ad351828ab" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" + > + Brexit + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" + > + May to address UK parliament on state of Brexit talks + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fc77408fa-d065-11e8-a9f2-7574db66bcd5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save May to address UK parliament on state of Brexit talks to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" + data-id="ed665ed8-cdfd-11e8-9fe5-24ad351828ab" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: UK business & economy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-business-economy" + > + UK business & economy + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" + > + Bradford hospital launches AI powered command centre + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" + tabIndex={-1} + > + Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F66d87a12-d06f-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" + className="ArticleSaveButton_root__Utel0" + data-content-id="ed665ed8-cdfd-11e8-9fe5-24ad351828ab" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Bradford hospital launches AI powered command centre to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="ed665ed8-cdfd-11e8-9fe5-24ad351828ab" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--opinion js-teaser" + data-id="7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <span + className="o-teaser__tag-prefix" + > + FT Alphaville + </span> + <a + aria-label="Category: Bryce Elder" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/markets/bryce-elder" + > + Bryce Elder + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="http://ftalphaville.ft.com/marketslive/2018-10-15/" + > + Markets Live: Monday, 15th October 2018 + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426" + className="ArticleSaveButton_root__Utel0" + data-content-id="7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Markets Live: Monday, 15th October 2018 to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + <li + className="TeaserTimeline_item__1s6ow" + > + <div + className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser js-teaser" + data-id="aa9b9bda-d056-11e8-a9f2-7574db66bcd5" + > + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: UK trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-trade" + > + UK trade + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" + > + Irish deputy PM voices ‘frustration’ at Brexit impasse + </a> + </div> + </div> + </div> + <div + className="TeaserTimeline_itemActions__1ao7c" + > + <form + action="/myft/save/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" + className="ArticleSaveButton_root__Utel0" + data-content-id="aa9b9bda-d056-11e8-a9f2-7574db66bcd5" + method="GET" + onSubmit={[Function]} + > + <button + aria-label="Save Irish deputy PM voices ‘frustration’ at Brexit impasse to myFT for later" + aria-pressed={false} + className="ArticleSaveButton_button__2_wUr" + data-content-id="aa9b9bda-d056-11e8-a9f2-7574db66bcd5" + data-trackable="save-for-later" + type="submit" + > + <span + className="ArticleSaveButton_icon__-7Con" + /> + Save + </button> + </form> + </div> + </li> + </ul> + </section> +</div> +`; diff --git a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap index abc1dd752..4115cc63c 100644 --- a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap +++ b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap @@ -26,17 +26,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -47,7 +44,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" > Is December looming as the new Brexit deadline? - </a> </div> <p @@ -78,7 +74,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -128,17 +124,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -149,7 +142,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" > May seeks to overcome deadlock after Brexit setback - </a> </div> <p @@ -180,7 +172,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -230,17 +222,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -251,7 +240,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" > Midsized UK businesses turn sour on Brexit - </a> </div> <p @@ -282,7 +270,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -332,17 +320,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -353,7 +338,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" > Barnier open to extending Brexit transition by a year - </a> </div> <p @@ -384,7 +368,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -434,22 +418,19 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + Inside Business + </span> + <a + aria-label="Category: Sarah Gordon" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/sarah-gordon" > - <span - className="o-teaser__tag-prefix" - > - Inside Business - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/sarah-gordon" - > - Sarah Gordon - </a> - </div> + Sarah Gordon + </a> </div> <div className="o-teaser__heading" @@ -460,7 +441,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" > UK lets down business with lack of Brexit advice - </a> </div> <p @@ -491,7 +471,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -541,17 +521,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/global-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/global-trade" - > - Global trade - </a> - </div> + Global trade + </a> </div> <div className="o-teaser__heading" @@ -562,7 +539,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" > Trump looks to start formal US-UK trade talks - </a> </div> <p @@ -593,7 +569,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -656,17 +632,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: House of Commons UK" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" - > - House of Commons UK - </a> - </div> + House of Commons UK + </a> </div> <div className="o-teaser__heading" @@ -677,7 +650,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" > Bercow faces mounting calls to resign - </a> </div> <p @@ -708,7 +680,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -758,22 +730,19 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - The FT View - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/ft-view" - > - The editorial board - </a> - </div> + The FT View + </span> + <a + aria-label="Category: The editorial board" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/ft-view" + > + The editorial board + </a> </div> <div className="o-teaser__heading" @@ -784,7 +753,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" > A culture shift to clear Westminster’s toxic air - </a> </div> <p @@ -815,7 +783,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -865,17 +833,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -886,7 +851,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" > EU demands UK break Brexit impasse - </a> </div> <p @@ -917,7 +881,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -967,17 +931,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -988,7 +949,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" > Brexit, Scotland and the threat to constitutional unity - </a> </div> <p @@ -1019,7 +979,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1069,17 +1029,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -1090,7 +1047,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" > EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse - </a> </div> </div> @@ -1109,7 +1065,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1159,17 +1115,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Wells Fargo" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" - > - Wells Fargo - </a> - </div> + Wells Fargo + </a> </div> <div className="o-teaser__heading" @@ -1180,7 +1133,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" > Wells Fargo applies for licence in France as part of Brexit strategy - </a> </div> </div> @@ -1199,7 +1151,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1249,17 +1201,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK welfare reform" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" - > - UK welfare reform - </a> - </div> + UK welfare reform + </a> </div> <div className="o-teaser__heading" @@ -1270,7 +1219,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" > Rollout of controversial UK welfare reform faces fresh delay - </a> </div> <p @@ -1301,7 +1249,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1351,17 +1299,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -1372,7 +1317,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" > Pound rallies after upbeat wage growth reading - </a> </div> <p @@ -1428,17 +1372,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK politics & policy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world/uk/politics" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world/uk/politics" - > - UK politics & policy - </a> - </div> + UK politics & policy + </a> </div> <div className="o-teaser__heading" @@ -1449,7 +1390,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" > Problem gambling shake-up set to be brought forward - </a> </div> <p @@ -1480,7 +1420,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1530,17 +1470,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Technology sector" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/technology" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/technology" - > - Technology sector - </a> - </div> + Technology sector + </a> </div> <div className="o-teaser__heading" @@ -1551,7 +1488,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" > Crypto exchange Coinbase sets up Brexit contingency - </a> </div> <p @@ -1582,7 +1518,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1645,17 +1581,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: World" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world" - > - World - </a> - </div> + World + </a> </div> <div className="o-teaser__heading" @@ -1666,7 +1599,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" > EU gives UK 24 hour Brexit breathing space - </a> </div> <p @@ -1697,7 +1629,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1747,17 +1679,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -1768,7 +1697,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" > EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ - </a> </div> </div> @@ -1812,17 +1740,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -1833,7 +1758,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" > Barnier plan no solution to Irish border, says Foster - </a> </div> </div> @@ -1852,7 +1776,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -1902,22 +1826,19 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + Explainer + </span> + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" > - <span - className="o-teaser__tag-prefix" - > - Explainer - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -1928,7 +1849,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/79939f94-c320-11e8-8d55-54197280d3f7" > Pound timeline: from the 1970s to Brexit crunch - </a> </div> <p @@ -1959,7 +1879,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/79939f94-c320-11e8-8d55-54197280d3f7" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2009,17 +1929,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -2030,7 +1947,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" > Brexit talks could drag into December warns Ireland’s Varadkar - </a> </div> <p @@ -2061,7 +1977,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2111,17 +2027,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -2132,7 +2045,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" > Crisis or choreography over Brexit? - </a> </div> <p @@ -2163,7 +2075,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2213,17 +2125,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Industrials" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/industrials" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/industrials" - > - Industrials - </a> - </div> + Industrials + </a> </div> <div className="o-teaser__heading" @@ -2234,7 +2143,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" > UK shipyards to submit bids to build 5 warships - </a> </div> <p @@ -2265,7 +2173,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2315,17 +2223,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -2336,7 +2241,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" > May to address UK parliament on state of Brexit talks - </a> </div> </div> @@ -2355,7 +2259,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2405,17 +2309,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK business & economy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-business-economy" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-business-economy" - > - UK business & economy - </a> - </div> + UK business & economy + </a> </div> <div className="o-teaser__heading" @@ -2426,7 +2327,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" > Bradford hospital launches AI powered command centre - </a> </div> <p @@ -2457,7 +2357,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes aria-hidden="true" data-trackable="image-link" href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2507,22 +2407,19 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + FT Alphaville + </span> + <a + aria-label="Category: Bryce Elder" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/markets/bryce-elder" > - <span - className="o-teaser__tag-prefix" - > - FT Alphaville - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/markets/bryce-elder" - > - Bryce Elder - </a> - </div> + Bryce Elder + </a> </div> <div className="o-teaser__heading" @@ -2533,7 +2430,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="http://ftalphaville.ft.com/marketslive/2018-10-15/" > Markets Live: Monday, 15th October 2018 - </a> </div> </div> @@ -2577,17 +2473,14 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-trade" - > - UK trade - </a> - </div> + UK trade + </a> </div> <div className="o-teaser__heading" @@ -2598,7 +2491,6 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes href="/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" > Irish deputy PM voices ‘frustration’ at Brexit impasse - </a> </div> </div> @@ -2660,17 +2552,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -2681,7 +2570,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" > Is December looming as the new Brexit deadline? - </a> </div> <p @@ -2712,7 +2600,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2762,17 +2650,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -2783,7 +2668,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" > May seeks to overcome deadlock after Brexit setback - </a> </div> <p @@ -2814,7 +2698,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2864,17 +2748,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -2885,7 +2766,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" > Midsized UK businesses turn sour on Brexit - </a> </div> <p @@ -2916,7 +2796,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -2966,17 +2846,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -2987,7 +2864,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" > Barnier open to extending Brexit transition by a year - </a> </div> <p @@ -3018,7 +2894,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3068,22 +2944,19 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - Inside Business - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/sarah-gordon" - > - Sarah Gordon - </a> - </div> + Inside Business + </span> + <a + aria-label="Category: Sarah Gordon" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/sarah-gordon" + > + Sarah Gordon + </a> </div> <div className="o-teaser__heading" @@ -3094,7 +2967,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" > UK lets down business with lack of Brexit advice - </a> </div> <p @@ -3125,7 +2997,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3175,17 +3047,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/global-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/global-trade" - > - Global trade - </a> - </div> + Global trade + </a> </div> <div className="o-teaser__heading" @@ -3196,7 +3065,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" > Trump looks to start formal US-UK trade talks - </a> </div> <p @@ -3227,7 +3095,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3290,17 +3158,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: House of Commons UK" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" - > - House of Commons UK - </a> - </div> + House of Commons UK + </a> </div> <div className="o-teaser__heading" @@ -3311,7 +3176,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" > Bercow faces mounting calls to resign - </a> </div> <p @@ -3342,7 +3206,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3392,22 +3256,19 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + The FT View + </span> + <a + aria-label="Category: The editorial board" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/ft-view" > - <span - className="o-teaser__tag-prefix" - > - The FT View - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/ft-view" - > - The editorial board - </a> - </div> + The editorial board + </a> </div> <div className="o-teaser__heading" @@ -3418,7 +3279,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" > A culture shift to clear Westminster’s toxic air - </a> </div> <p @@ -3449,7 +3309,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3499,17 +3359,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -3520,7 +3377,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" > EU demands UK break Brexit impasse - </a> </div> <p @@ -3551,7 +3407,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3601,17 +3457,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -3622,7 +3475,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" > Brexit, Scotland and the threat to constitutional unity - </a> </div> <p @@ -3653,7 +3505,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3703,17 +3555,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -3724,7 +3573,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" > EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse - </a> </div> </div> @@ -3743,7 +3591,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3793,17 +3641,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Wells Fargo" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" - > - Wells Fargo - </a> - </div> + Wells Fargo + </a> </div> <div className="o-teaser__heading" @@ -3814,7 +3659,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" > Wells Fargo applies for licence in France as part of Brexit strategy - </a> </div> </div> @@ -3833,7 +3677,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3883,17 +3727,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK welfare reform" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" - > - UK welfare reform - </a> - </div> + UK welfare reform + </a> </div> <div className="o-teaser__heading" @@ -3904,7 +3745,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" > Rollout of controversial UK welfare reform faces fresh delay - </a> </div> <p @@ -3935,7 +3775,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -3985,17 +3825,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -4006,7 +3843,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" > Pound rallies after upbeat wage growth reading - </a> </div> <p @@ -4062,17 +3898,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK politics & policy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world/uk/politics" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world/uk/politics" - > - UK politics & policy - </a> - </div> + UK politics & policy + </a> </div> <div className="o-teaser__heading" @@ -4083,7 +3916,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" > Problem gambling shake-up set to be brought forward - </a> </div> <p @@ -4114,7 +3946,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4164,17 +3996,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Technology sector" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/technology" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/technology" - > - Technology sector - </a> - </div> + Technology sector + </a> </div> <div className="o-teaser__heading" @@ -4185,7 +4014,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" > Crypto exchange Coinbase sets up Brexit contingency - </a> </div> <p @@ -4216,7 +4044,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4279,17 +4107,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: World" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world" - > - World - </a> - </div> + World + </a> </div> <div className="o-teaser__heading" @@ -4300,7 +4125,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" > EU gives UK 24 hour Brexit breathing space - </a> </div> <p @@ -4331,7 +4155,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4381,17 +4205,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -4402,7 +4223,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" > EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ - </a> </div> </div> @@ -4446,17 +4266,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -4467,7 +4284,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" > Barnier plan no solution to Irish border, says Foster - </a> </div> </div> @@ -4486,7 +4302,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4536,22 +4352,19 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - Explainer - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Explainer + </span> + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" + > + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -4562,7 +4375,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/79939f94-c320-11e8-8d55-54197280d3f7" > Pound timeline: from the 1970s to Brexit crunch - </a> </div> <p @@ -4593,7 +4405,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/79939f94-c320-11e8-8d55-54197280d3f7" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4643,17 +4455,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -4664,7 +4473,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" > Brexit talks could drag into December warns Ireland’s Varadkar - </a> </div> <p @@ -4695,7 +4503,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4745,17 +4553,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -4766,7 +4571,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" > Crisis or choreography over Brexit? - </a> </div> <p @@ -4797,7 +4601,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4847,17 +4651,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Industrials" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/industrials" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/industrials" - > - Industrials - </a> - </div> + Industrials + </a> </div> <div className="o-teaser__heading" @@ -4868,7 +4669,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" > UK shipyards to submit bids to build 5 warships - </a> </div> <p @@ -4899,7 +4699,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -4949,17 +4749,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -4970,7 +4767,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" > May to address UK parliament on state of Brexit talks - </a> </div> </div> @@ -4989,7 +4785,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -5039,17 +4835,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK business & economy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-business-economy" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-business-economy" - > - UK business & economy - </a> - </div> + UK business & economy + </a> </div> <div className="o-teaser__heading" @@ -5060,7 +4853,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" > Bradford hospital launches AI powered command centre - </a> </div> <p @@ -5091,7 +4883,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as aria-hidden="true" data-trackable="image-link" href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -5141,22 +4933,19 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Alphaville - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/markets/bryce-elder" - > - Bryce Elder - </a> - </div> + FT Alphaville + </span> + <a + aria-label="Category: Bryce Elder" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/markets/bryce-elder" + > + Bryce Elder + </a> </div> <div className="o-teaser__heading" @@ -5167,7 +4956,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="http://ftalphaville.ft.com/marketslive/2018-10-15/" > Markets Live: Monday, 15th October 2018 - </a> </div> </div> @@ -5211,17 +4999,14 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-trade" - > - UK trade - </a> - </div> + UK trade + </a> </div> <div className="o-teaser__heading" @@ -5232,7 +5017,6 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as href="/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" > Irish deputy PM voices ‘frustration’ at Brexit impasse - </a> </div> </div> @@ -5294,17 +5078,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -5315,7 +5096,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" > Is December looming as the new Brexit deadline? - </a> </div> <p @@ -5346,7 +5126,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -5396,17 +5176,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -5417,7 +5194,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" > May seeks to overcome deadlock after Brexit setback - </a> </div> <p @@ -5448,7 +5224,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -5511,17 +5287,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -5532,7 +5305,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" > Midsized UK businesses turn sour on Brexit - </a> </div> <p @@ -5563,7 +5335,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -5613,17 +5385,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -5634,7 +5403,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" > Barnier open to extending Brexit transition by a year - </a> </div> <p @@ -5665,7 +5433,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -5715,22 +5483,19 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + Inside Business + </span> + <a + aria-label="Category: Sarah Gordon" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/sarah-gordon" > - <span - className="o-teaser__tag-prefix" - > - Inside Business - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/sarah-gordon" - > - Sarah Gordon - </a> - </div> + Sarah Gordon + </a> </div> <div className="o-teaser__heading" @@ -5741,7 +5506,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" > UK lets down business with lack of Brexit advice - </a> </div> <p @@ -5772,7 +5536,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -5822,17 +5586,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/global-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/global-trade" - > - Global trade - </a> - </div> + Global trade + </a> </div> <div className="o-teaser__heading" @@ -5843,7 +5604,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" > Trump looks to start formal US-UK trade talks - </a> </div> <p @@ -5874,7 +5634,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -5937,17 +5697,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: House of Commons UK" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" - > - House of Commons UK - </a> - </div> + House of Commons UK + </a> </div> <div className="o-teaser__heading" @@ -5958,7 +5715,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" > Bercow faces mounting calls to resign - </a> </div> <p @@ -5989,7 +5745,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6039,22 +5795,19 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - The FT View - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/ft-view" - > - The editorial board - </a> - </div> + The FT View + </span> + <a + aria-label="Category: The editorial board" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/ft-view" + > + The editorial board + </a> </div> <div className="o-teaser__heading" @@ -6065,7 +5818,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" > A culture shift to clear Westminster’s toxic air - </a> </div> <p @@ -6096,7 +5848,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6146,17 +5898,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -6167,7 +5916,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" > EU demands UK break Brexit impasse - </a> </div> <p @@ -6198,7 +5946,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6248,17 +5996,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -6269,7 +6014,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" > Brexit, Scotland and the threat to constitutional unity - </a> </div> <p @@ -6300,7 +6044,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6350,17 +6094,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -6371,7 +6112,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" > EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse - </a> </div> </div> @@ -6390,7 +6130,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6440,17 +6180,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Wells Fargo" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" - > - Wells Fargo - </a> - </div> + Wells Fargo + </a> </div> <div className="o-teaser__heading" @@ -6461,7 +6198,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" > Wells Fargo applies for licence in France as part of Brexit strategy - </a> </div> </div> @@ -6480,7 +6216,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6530,17 +6266,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK welfare reform" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" - > - UK welfare reform - </a> - </div> + UK welfare reform + </a> </div> <div className="o-teaser__heading" @@ -6551,7 +6284,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" > Rollout of controversial UK welfare reform faces fresh delay - </a> </div> <p @@ -6582,7 +6314,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6632,17 +6364,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -6653,7 +6382,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" > Pound rallies after upbeat wage growth reading - </a> </div> <p @@ -6709,17 +6437,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK politics & policy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world/uk/politics" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world/uk/politics" - > - UK politics & policy - </a> - </div> + UK politics & policy + </a> </div> <div className="o-teaser__heading" @@ -6730,7 +6455,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" > Problem gambling shake-up set to be brought forward - </a> </div> <p @@ -6761,7 +6485,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6811,17 +6535,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Technology sector" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/technology" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/technology" - > - Technology sector - </a> - </div> + Technology sector + </a> </div> <div className="o-teaser__heading" @@ -6832,7 +6553,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" > Crypto exchange Coinbase sets up Brexit contingency - </a> </div> <p @@ -6863,7 +6583,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -6926,17 +6646,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: World" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world" - > - World - </a> - </div> + World + </a> </div> <div className="o-teaser__heading" @@ -6947,7 +6664,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" > EU gives UK 24 hour Brexit breathing space - </a> </div> <p @@ -6978,7 +6694,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -7028,17 +6744,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -7049,7 +6762,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" > EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ - </a> </div> </div> @@ -7093,17 +6805,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -7114,7 +6823,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" > Barnier plan no solution to Irish border, says Foster - </a> </div> </div> @@ -7133,7 +6841,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -7183,22 +6891,19 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - Explainer - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Explainer + </span> + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" + > + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -7209,7 +6914,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/79939f94-c320-11e8-8d55-54197280d3f7" > Pound timeline: from the 1970s to Brexit crunch - </a> </div> <p @@ -7240,7 +6944,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/79939f94-c320-11e8-8d55-54197280d3f7" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -7290,17 +6994,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -7311,7 +7012,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" > Brexit talks could drag into December warns Ireland’s Varadkar - </a> </div> <p @@ -7342,7 +7042,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -7392,17 +7092,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -7413,7 +7110,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" > Crisis or choreography over Brexit? - </a> </div> <p @@ -7444,7 +7140,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -7494,17 +7190,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Industrials" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/industrials" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/industrials" - > - Industrials - </a> - </div> + Industrials + </a> </div> <div className="o-teaser__heading" @@ -7515,7 +7208,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" > UK shipyards to submit bids to build 5 warships - </a> </div> <p @@ -7546,7 +7238,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -7596,17 +7288,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -7617,7 +7306,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" > May to address UK parliament on state of Brexit talks - </a> </div> </div> @@ -7636,7 +7324,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -7686,17 +7374,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK business & economy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-business-economy" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-business-economy" - > - UK business & economy - </a> - </div> + UK business & economy + </a> </div> <div className="o-teaser__heading" @@ -7707,7 +7392,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" > Bradford hospital launches AI powered command centre - </a> </div> <p @@ -7738,7 +7422,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, aria-hidden="true" data-trackable="image-link" href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -7788,22 +7472,19 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + FT Alphaville + </span> + <a + aria-label="Category: Bryce Elder" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/markets/bryce-elder" > - <span - className="o-teaser__tag-prefix" - > - FT Alphaville - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/markets/bryce-elder" - > - Bryce Elder - </a> - </div> + Bryce Elder + </a> </div> <div className="o-teaser__heading" @@ -7814,7 +7495,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="http://ftalphaville.ft.com/marketslive/2018-10-15/" > Markets Live: Monday, 15th October 2018 - </a> </div> </div> @@ -7858,17 +7538,14 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-trade" - > - UK trade - </a> - </div> + UK trade + </a> </div> <div className="o-teaser__heading" @@ -7879,7 +7556,6 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier, href="/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" > Irish deputy PM voices ‘frustration’ at Brexit impasse - </a> </div> </div> @@ -7941,17 +7617,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -7962,7 +7635,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" > Is December looming as the new Brexit deadline? - </a> </div> <p @@ -7993,7 +7665,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8043,17 +7715,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -8064,7 +7733,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" > May seeks to overcome deadlock after Brexit setback - </a> </div> <p @@ -8095,7 +7763,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8145,17 +7813,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -8166,7 +7831,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" > Midsized UK businesses turn sour on Brexit - </a> </div> <p @@ -8197,7 +7861,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8247,17 +7911,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -8268,7 +7929,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" > Barnier open to extending Brexit transition by a year - </a> </div> <p @@ -8299,7 +7959,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8349,22 +8009,19 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + Inside Business + </span> + <a + aria-label="Category: Sarah Gordon" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/sarah-gordon" > - <span - className="o-teaser__tag-prefix" - > - Inside Business - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/sarah-gordon" - > - Sarah Gordon - </a> - </div> + Sarah Gordon + </a> </div> <div className="o-teaser__heading" @@ -8375,7 +8032,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" > UK lets down business with lack of Brexit advice - </a> </div> <p @@ -8406,7 +8062,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8456,17 +8112,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/global-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/global-trade" - > - Global trade - </a> - </div> + Global trade + </a> </div> <div className="o-teaser__heading" @@ -8477,7 +8130,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" > Trump looks to start formal US-UK trade talks - </a> </div> <p @@ -8508,7 +8160,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8571,17 +8223,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: House of Commons UK" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" - > - House of Commons UK - </a> - </div> + House of Commons UK + </a> </div> <div className="o-teaser__heading" @@ -8592,7 +8241,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" > Bercow faces mounting calls to resign - </a> </div> <p @@ -8623,7 +8271,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8673,22 +8321,19 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + The FT View + </span> + <a + aria-label="Category: The editorial board" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/ft-view" > - <span - className="o-teaser__tag-prefix" - > - The FT View - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/ft-view" - > - The editorial board - </a> - </div> + The editorial board + </a> </div> <div className="o-teaser__heading" @@ -8699,7 +8344,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" > A culture shift to clear Westminster’s toxic air - </a> </div> <p @@ -8730,7 +8374,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8780,17 +8424,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -8801,7 +8442,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" > EU demands UK break Brexit impasse - </a> </div> <p @@ -8832,7 +8472,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8882,17 +8522,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -8903,7 +8540,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" > Brexit, Scotland and the threat to constitutional unity - </a> </div> <p @@ -8934,7 +8570,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -8984,17 +8620,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -9005,7 +8638,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" > EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse - </a> </div> </div> @@ -9024,7 +8656,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -9074,17 +8706,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Wells Fargo" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" - > - Wells Fargo - </a> - </div> + Wells Fargo + </a> </div> <div className="o-teaser__heading" @@ -9095,7 +8724,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" > Wells Fargo applies for licence in France as part of Brexit strategy - </a> </div> </div> @@ -9114,7 +8742,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -9164,17 +8792,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK welfare reform" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" - > - UK welfare reform - </a> - </div> + UK welfare reform + </a> </div> <div className="o-teaser__heading" @@ -9185,7 +8810,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" > Rollout of controversial UK welfare reform faces fresh delay - </a> </div> <p @@ -9216,7 +8840,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -9266,17 +8890,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -9287,7 +8908,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" > Pound rallies after upbeat wage growth reading - </a> </div> <p @@ -9343,17 +8963,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK politics & policy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world/uk/politics" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world/uk/politics" - > - UK politics & policy - </a> - </div> + UK politics & policy + </a> </div> <div className="o-teaser__heading" @@ -9364,7 +8981,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" > Problem gambling shake-up set to be brought forward - </a> </div> <p @@ -9395,7 +9011,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -9445,17 +9061,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Technology sector" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/technology" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/technology" - > - Technology sector - </a> - </div> + Technology sector + </a> </div> <div className="o-teaser__heading" @@ -9466,7 +9079,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" > Crypto exchange Coinbase sets up Brexit contingency - </a> </div> <p @@ -9497,7 +9109,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -9560,17 +9172,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: World" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world" - > - World - </a> - </div> + World + </a> </div> <div className="o-teaser__heading" @@ -9581,7 +9190,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" > EU gives UK 24 hour Brexit breathing space - </a> </div> <p @@ -9612,7 +9220,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -9662,17 +9270,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -9683,7 +9288,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" > EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ - </a> </div> </div> @@ -9727,17 +9331,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -9748,7 +9349,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" > Barnier plan no solution to Irish border, says Foster - </a> </div> </div> @@ -9767,7 +9367,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -9817,22 +9417,19 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - Explainer - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Explainer + </span> + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" + > + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -9843,7 +9440,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/79939f94-c320-11e8-8d55-54197280d3f7" > Pound timeline: from the 1970s to Brexit crunch - </a> </div> <p @@ -9874,7 +9470,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/79939f94-c320-11e8-8d55-54197280d3f7" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -9924,17 +9520,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -9945,7 +9538,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" > Brexit talks could drag into December warns Ireland’s Varadkar - </a> </div> <p @@ -9976,7 +9568,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10026,17 +9618,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -10047,7 +9636,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" > Crisis or choreography over Brexit? - </a> </div> <p @@ -10078,7 +9666,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10128,17 +9716,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Industrials" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/industrials" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/industrials" - > - Industrials - </a> - </div> + Industrials + </a> </div> <div className="o-teaser__heading" @@ -10149,7 +9734,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" > UK shipyards to submit bids to build 5 warships - </a> </div> <p @@ -10180,7 +9764,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10230,17 +9814,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -10251,7 +9832,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" > May to address UK parliament on state of Brexit talks - </a> </div> </div> @@ -10270,7 +9850,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10320,17 +9900,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK business & economy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-business-economy" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-business-economy" - > - UK business & economy - </a> - </div> + UK business & economy + </a> </div> <div className="o-teaser__heading" @@ -10341,7 +9918,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" > Bradford hospital launches AI powered command centre - </a> </div> <p @@ -10372,7 +9948,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i aria-hidden="true" data-trackable="image-link" href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10422,22 +9998,19 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Alphaville - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/markets/bryce-elder" - > - Bryce Elder - </a> - </div> + FT Alphaville + </span> + <a + aria-label="Category: Bryce Elder" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/markets/bryce-elder" + > + Bryce Elder + </a> </div> <div className="o-teaser__heading" @@ -10448,7 +10021,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="http://ftalphaville.ft.com/marketslive/2018-10-15/" > Markets Live: Monday, 15th October 2018 - </a> </div> </div> @@ -10492,17 +10064,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-trade" - > - UK trade - </a> - </div> + UK trade + </a> </div> <div className="o-teaser__heading" @@ -10513,7 +10082,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i href="/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" > Irish deputy PM voices ‘frustration’ at Brexit impasse - </a> </div> </div> @@ -10575,17 +10143,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -10596,7 +10161,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" > Is December looming as the new Brexit deadline? - </a> </div> <p @@ -10627,7 +10191,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10652,17 +10216,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -10673,7 +10234,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" > May seeks to overcome deadlock after Brexit setback - </a> </div> <p @@ -10704,7 +10264,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10729,17 +10289,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -10750,7 +10307,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" > Midsized UK businesses turn sour on Brexit - </a> </div> <p @@ -10781,7 +10337,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10806,17 +10362,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -10827,7 +10380,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" > Barnier open to extending Brexit transition by a year - </a> </div> <p @@ -10858,7 +10410,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10883,22 +10435,19 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" + > + Inside Business + </span> + <a + aria-label="Category: Sarah Gordon" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/sarah-gordon" > - <span - className="o-teaser__tag-prefix" - > - Inside Business - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/sarah-gordon" - > - Sarah Gordon - </a> - </div> + Sarah Gordon + </a> </div> <div className="o-teaser__heading" @@ -10909,7 +10458,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" > UK lets down business with lack of Brexit advice - </a> </div> <p @@ -10940,7 +10488,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -10965,17 +10513,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Global trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/global-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/global-trade" - > - Global trade - </a> - </div> + Global trade + </a> </div> <div className="o-teaser__heading" @@ -10986,7 +10531,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" > Trump looks to start formal US-UK trade talks - </a> </div> <p @@ -11017,7 +10561,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11055,17 +10599,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: House of Commons UK" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" - > - House of Commons UK - </a> - </div> + House of Commons UK + </a> </div> <div className="o-teaser__heading" @@ -11076,7 +10617,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" > Bercow faces mounting calls to resign - </a> </div> <p @@ -11107,7 +10647,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11132,22 +10672,19 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - The FT View - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/ft-view" - > - The editorial board - </a> - </div> + The FT View + </span> + <a + aria-label="Category: The editorial board" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/ft-view" + > + The editorial board + </a> </div> <div className="o-teaser__heading" @@ -11158,7 +10695,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" > A culture shift to clear Westminster’s toxic air - </a> </div> <p @@ -11189,7 +10725,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11214,17 +10750,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -11235,7 +10768,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" > EU demands UK break Brexit impasse - </a> </div> <p @@ -11266,7 +10798,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11291,17 +10823,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -11312,7 +10841,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" > Brexit, Scotland and the threat to constitutional unity - </a> </div> <p @@ -11343,7 +10871,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11368,17 +10896,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -11389,7 +10914,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" > EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse - </a> </div> </div> @@ -11408,7 +10932,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11433,17 +10957,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Wells Fargo" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" - > - Wells Fargo - </a> - </div> + Wells Fargo + </a> </div> <div className="o-teaser__heading" @@ -11454,7 +10975,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" > Wells Fargo applies for licence in France as part of Brexit strategy - </a> </div> </div> @@ -11473,7 +10993,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11498,17 +11018,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK welfare reform" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" - > - UK welfare reform - </a> - </div> + UK welfare reform + </a> </div> <div className="o-teaser__heading" @@ -11519,7 +11036,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" > Rollout of controversial UK welfare reform faces fresh delay - </a> </div> <p @@ -11550,7 +11066,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11575,17 +11091,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -11596,7 +11109,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" > Pound rallies after upbeat wage growth reading - </a> </div> <p @@ -11627,17 +11139,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK politics & policy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world/uk/politics" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world/uk/politics" - > - UK politics & policy - </a> - </div> + UK politics & policy + </a> </div> <div className="o-teaser__heading" @@ -11648,7 +11157,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" > Problem gambling shake-up set to be brought forward - </a> </div> <p @@ -11679,7 +11187,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11704,17 +11212,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Technology sector" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/technology" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/technology" - > - Technology sector - </a> - </div> + Technology sector + </a> </div> <div className="o-teaser__heading" @@ -11725,7 +11230,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" > Crypto exchange Coinbase sets up Brexit contingency - </a> </div> <p @@ -11756,7 +11260,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11794,17 +11298,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: World" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/world" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world" - > - World - </a> - </div> + World + </a> </div> <div className="o-teaser__heading" @@ -11815,7 +11316,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" > EU gives UK 24 hour Brexit breathing space - </a> </div> <p @@ -11846,7 +11346,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11871,17 +11371,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -11892,7 +11389,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" > EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ - </a> </div> </div> @@ -11911,17 +11407,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -11932,7 +11425,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" > Barnier plan no solution to Irish border, says Foster - </a> </div> </div> @@ -11951,7 +11443,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -11976,22 +11468,19 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - Explainer - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> + Explainer + </span> + <a + aria-label="Category: Pound Sterling" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" + > + Pound Sterling + </a> </div> <div className="o-teaser__heading" @@ -12002,7 +11491,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/79939f94-c320-11e8-8d55-54197280d3f7" > Pound timeline: from the 1970s to Brexit crunch - </a> </div> <p @@ -12033,7 +11521,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/79939f94-c320-11e8-8d55-54197280d3f7" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -12058,17 +11546,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -12079,7 +11564,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" > Brexit talks could drag into December warns Ireland’s Varadkar - </a> </div> <p @@ -12110,7 +11594,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -12135,17 +11619,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit Briefing" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit-briefing" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> + Brexit Briefing + </a> </div> <div className="o-teaser__heading" @@ -12156,7 +11637,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" > Crisis or choreography over Brexit? - </a> </div> <p @@ -12187,7 +11667,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -12212,17 +11692,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Industrials" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/companies/industrials" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/industrials" - > - Industrials - </a> - </div> + Industrials + </a> </div> <div className="o-teaser__heading" @@ -12233,7 +11710,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" > UK shipyards to submit bids to build 5 warships - </a> </div> <p @@ -12264,7 +11740,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -12289,17 +11765,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: Brexit" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/brexit" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> + Brexit + </a> </div> <div className="o-teaser__heading" @@ -12310,7 +11783,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" > May to address UK parliament on state of Brexit talks - </a> </div> </div> @@ -12329,7 +11801,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -12354,17 +11826,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK business & economy" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-business-economy" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-business-economy" - > - UK business & economy - </a> - </div> + UK business & economy + </a> </div> <div className="o-teaser__heading" @@ -12375,7 +11844,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" > Bradford hospital launches AI powered command centre - </a> </div> <p @@ -12406,7 +11874,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false aria-hidden="true" data-trackable="image-link" href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - tab-index="-1" + tabIndex="-1" > <img alt="" @@ -12431,22 +11899,19 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <span + className="o-teaser__tag-prefix" > - <span - className="o-teaser__tag-prefix" - > - FT Alphaville - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/markets/bryce-elder" - > - Bryce Elder - </a> - </div> + FT Alphaville + </span> + <a + aria-label="Category: Bryce Elder" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/markets/bryce-elder" + > + Bryce Elder + </a> </div> <div className="o-teaser__heading" @@ -12457,7 +11922,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="http://ftalphaville.ft.com/marketslive/2018-10-15/" > Markets Live: Monday, 15th October 2018 - </a> </div> </div> @@ -12476,17 +11940,14 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false <div className="o-teaser__meta" > - <div - className="o-teaser__meta-tag" + <a + aria-label="Category: UK trade" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="/uk-trade" > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-trade" - > - UK trade - </a> - </div> + UK trade + </a> </div> <div className="o-teaser__heading" @@ -12497,7 +11958,6 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false href="/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" > Irish deputy PM voices ‘frustration’ at Brexit impasse - </a> </div> </div> From 20d7b7279c116c495fb58c8b41196a78827b5d9e Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Wed, 6 Nov 2019 10:46:50 +0000 Subject: [PATCH 260/760] Add a description to package --- components/x-teaser-timeline/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index a774ff4e0..2aa3662bb 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -1,7 +1,7 @@ { "name": "@financial-times/x-teaser-timeline", "version": "0.0.0", - "description": "", + "description": "Display a list of teasers grouped by day and/or last visit time", "main": "dist/TeaserTimeline.cjs.js", "module": "dist/TeaserTimeline.esm.js", "browser": "dist/TeaserTimeline.es5.js", From d29e6326171c202ba323d14d2e799b035a06487a Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Wed, 6 Nov 2019 10:58:56 +0000 Subject: [PATCH 261/760] Register x-teaser-timeline in storybook --- .storybook/register-components.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 06ad9288b..b93d545fb 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -5,6 +5,7 @@ const components = [ require('../components/x-styling-demo/stories'), require('../components/x-gift-article/stories'), require('../components/x-podcast-launchers/stories'), + require('../components/x-teaser-timeline/stories'), ]; module.exports = components; From 3c942a7854ef99a28553d02987ffb963d3374604 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Wed, 6 Nov 2019 12:51:26 +0000 Subject: [PATCH 262/760] Add hack to disable babel-plugin-minify-simplify plugin implemented by @babel/preset-minify as it cannot currently handle restful destructuring assignment --- .storybook/webpack.config.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 0f8b49005..fcd6c8161 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -50,6 +50,23 @@ module.exports = ({ config }) => { } ]; + // HACK: there is a bug in babel-plugin-minify-simplify which cannot + // handle how Babel transpiles restful destructing assignment. + // e.g. const { foo, ...qux } = { foo: 0, bar: 1, baz: 2 } + babelConfig.options.presets.find((preset) => { + let name = preset; + let options; + + if (Array.isArray(preset)) { + name = preset[0]; + options = preset[1]; + } + + if (name.includes('babel-preset-minify') && options) { + options.simplify = false; + }; + }); + // HACK: Ensure we only bundle one instance of React config.resolve.alias.react = require.resolve('react'); From 98145f753437231a7dfc38c156860bdbba25e2b0 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <matt.hinchliffe@ft.com> Date: Wed, 6 Nov 2019 12:53:51 +0000 Subject: [PATCH 263/760] Remove babel-preset-minify from Storybook configuration as it is broken --- .storybook/webpack.config.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index fcd6c8161..b419c439b 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -51,20 +51,11 @@ module.exports = ({ config }) => { ]; // HACK: there is a bug in babel-plugin-minify-simplify which cannot - // handle how Babel transpiles restful destructing assignment. + // handle how Babel transpiles restful destructing assignment so remove it. // e.g. const { foo, ...qux } = { foo: 0, bar: 1, baz: 2 } - babelConfig.options.presets.find((preset) => { - let name = preset; - let options; - - if (Array.isArray(preset)) { - name = preset[0]; - options = preset[1]; - } - - if (name.includes('babel-preset-minify') && options) { - options.simplify = false; - }; + babelConfig.options.presets = babelConfig.options.presets.filter((preset) => { + const name = Array.isArray(preset) ? preset[0] : preset; + return name.includes('babel-preset-minify') === false; }); // HACK: Ensure we only bundle one instance of React From faea6319307004ba99df66b1d0ccb0560c77bd8c Mon Sep 17 00:00:00 2001 From: Ben Barnett <ben@brushfiredesign.net> Date: Wed, 6 Nov 2019 15:30:40 +0000 Subject: [PATCH 264/760] Satisfy eslint's describe() demands --- __tests__/snapshots.test.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/__tests__/snapshots.test.js b/__tests__/snapshots.test.js index e2c685598..692fe6227 100644 --- a/__tests__/snapshots.test.js +++ b/__tests__/snapshots.test.js @@ -21,16 +21,14 @@ for(const pkg of packageDirs) { const { presets = { default: {} } } = require(pkgDir); const name = path.basename(pkg.name); - describe(`${pkg.name}`, () => { - for (const { title, data } of stories) { - for (const [preset, options] of Object.entries(presets)) { - it(`renders a ${preset} ${title} ${name}`, () => { - const props = { ...data, ...options }; - const tree = renderer.create(h(component, props)).toJSON(); - expect(tree).toMatchSnapshot(); - }); - } + for (const { title, data } of stories) { + for (const [preset, options] of Object.entries(presets)) { + it(`${pkg.name} renders a ${preset} ${title} ${name}`, () => { + const props = { ...data, ...options }; + const tree = renderer.create(h(component, props)).toJSON(); + expect(tree).toMatchSnapshot(); + }); } - }); + } } } From 41eaed9640e47defb1ab22b2f5d70793a3d29cae Mon Sep 17 00:00:00 2001 From: Katerina <katerina.loschinina@ft.com> Date: Thu, 24 May 2018 16:17:14 +0100 Subject: [PATCH 265/760] Original development of x-follow-button using x-interaction (largely obsolete). --- .../__snapshots__/snapshots.test.js.snap | 4670 ++++++++++++++++- components/x-follow-button/.bowerrc | 8 + components/x-follow-button/bower.json | 9 + components/x-follow-button/package.json | 31 + components/x-follow-button/readme.md | 107 + components/x-follow-button/rollup.js | 4 + components/x-follow-button/src/Button.jsx | 32 + .../x-follow-button/src/FollowButton.jsx | 66 + .../src/styles/components/FollowButton.scss | 15 + .../x-follow-button/src/styles/main.scss | 7 + .../src/styles/mixins/lozenge/_themes.scss | 42 + .../styles/mixins/lozenge/_toggle-icon.scss | 43 + .../src/styles/mixins/lozenge/main.scss | 64 + .../x-follow-button/stories/follow-button.js | 27 + components/x-follow-button/stories/index.js | 14 + components/x-follow-button/stories/knobs.js | 42 + components/x-styling-demo/.gitignore | 1 + components/x-styling-demo/rollup.config.js | 6 + .../blueprints/component/stories/example.js | 2 + tools/x-workbench/package.json | 0 tools/x-workbench/register-components.js | 0 .../x-workbench/static/components/.gitignore | 2 + web/gatsby-config.js | 8 +- 23 files changed, 5195 insertions(+), 5 deletions(-) create mode 100644 components/x-follow-button/.bowerrc create mode 100644 components/x-follow-button/bower.json create mode 100644 components/x-follow-button/package.json create mode 100644 components/x-follow-button/readme.md create mode 100644 components/x-follow-button/rollup.js create mode 100644 components/x-follow-button/src/Button.jsx create mode 100644 components/x-follow-button/src/FollowButton.jsx create mode 100644 components/x-follow-button/src/styles/components/FollowButton.scss create mode 100644 components/x-follow-button/src/styles/main.scss create mode 100644 components/x-follow-button/src/styles/mixins/lozenge/_themes.scss create mode 100644 components/x-follow-button/src/styles/mixins/lozenge/_toggle-icon.scss create mode 100644 components/x-follow-button/src/styles/mixins/lozenge/main.scss create mode 100644 components/x-follow-button/stories/follow-button.js create mode 100644 components/x-follow-button/stories/index.js create mode 100644 components/x-follow-button/stories/knobs.js create mode 100644 components/x-styling-demo/.gitignore create mode 100644 components/x-styling-demo/rollup.config.js create mode 100644 tools/x-workbench/package.json create mode 100644 tools/x-workbench/register-components.js create mode 100644 tools/x-workbench/static/components/.gitignore diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 5e0cd32f6..70f38a348 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -1,6 +1,4674 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`@financial-times/x-gift-article renders a default Free article x-gift-article 1`] = ` +exports[`@financial-times/x-follow-button renders a default Follow Button x-follow-button 1`] = ` +<div + data-x-dash-id="" +> + <form + action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" + data-concept-id="00000-0000-00000-00000" + data-myft-ui="follow" + data-myft-ui-variant={true} + method="GET" + onSubmit={[Function]} + > + <input + data-myft-csrf-token={true} + name="token" + type="hidden" + value="testTokenValue" + /> + <button + aria-label="Add Test Name to MyFT" + aria-pressed="false" + className="main_button__3Mk67 undefined" + data-alternate-label="Remove Test Name from MyFT" + data-alternate-text="Added" + data-concept-id="00000-0000-00000-00000" + data-trackable="follow" + data-trackable-context-messaging="add-to-myft-plus-digest-button" + onClick={[Function]} + title="Add Test Name to MyFT" + type="submit" + > + Add to myFT + </button> + </form> +</div> +`; + +exports[`@financial-times/x-increment renders a default Async x-increment 1`] = ` +<div> + <span> + 1 + </span> + <button + disabled={false} + onClick={[Function]} + > + Increment + </button> +</div> +`; + +exports[`@financial-times/x-increment renders a default Increment x-increment 1`] = ` +<div> + <span> + 1 + </span> + <button + disabled={false} + onClick={[Function]} + > + Increment + </button> +</div> +`; + +exports[`@financial-times/x-styling-demo renders a default Styling x-styling-demo 1`] = ` +<button + className="Button_button__vS7Mv" +> + Click me! +</button> +`; + +exports[`@financial-times/x-teaser renders a Hero Article x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Hero Content Package x-teaser 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Hero Opinion Piece x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" + width={75} + /> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Hero Package item x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Hero Paid Post x-teaser 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + + </a> + </div> + <p + className="o-teaser__standfirst" + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Hero Podcast x-teaser 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "100.0000%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Hero Top Story x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`@financial-times/x-teaser renders a Hero Video x-teaser 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__video" + > + <div + className="o--if-js" + > + <div + className="o-teaser__image-container js-image-container" + > + <div + className="o-video" + data-o-component="o-video" + data-o-video-autorender="true" + data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" + data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-placeholder="true" + data-o-video-placeholder-hint="Play video" + data-o-video-placeholder-info="[]" + data-o-video-playsinline="true" + /> + </div> + </div> + <div + className="o--if-no-js" + > + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroNarrow Article x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroNarrow Content Package x-teaser 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroNarrow Opinion Piece x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" + width={75} + /> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroNarrow Package item x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroNarrow Paid Post x-teaser 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + + </a> + </div> + <p + className="o-teaser__standfirst" + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroNarrow Podcast x-teaser 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "100.0000%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroNarrow Top Story x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroNarrow Video x-teaser 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__video" + > + <div + className="o--if-js" + > + <div + className="o-teaser__image-container js-image-container" + > + <div + className="o-video" + data-o-component="o-video" + data-o-video-autorender="true" + data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" + data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-placeholder="true" + data-o-video-placeholder-hint="Play video" + data-o-video-placeholder-info="[]" + data-o-video-playsinline="true" + /> + </div> + </div> + <div + className="o--if-no-js" + > + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + + </a> + </div> + <p + className="o-teaser__standfirst" + > + The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs + </p> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroOverlay Article x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroOverlay Content Package x-teaser 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroOverlay Opinion Piece x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-headshot o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" + width={75} + /> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroOverlay Package item x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroOverlay Paid Post x-teaser 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + + </a> + </div> + <p + className="o-teaser__standfirst" + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroOverlay Podcast x-teaser 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "100.0000%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroOverlay Top Story x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroOverlay Video x-teaser 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--hero o-teaser--hero-image o-teaser--has-video js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__video" + > + <div + className="o--if-js" + > + <div + className="o-teaser__image-container js-image-container" + > + <div + className="o-video" + data-o-component="o-video" + data-o-video-autorender="true" + data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" + data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-placeholder="true" + data-o-video-placeholder-hint="Play video" + data-o-video-placeholder-info="[]" + data-o-video-playsinline="true" + /> + </div> + </div> + <div + className="o--if-no-js" + > + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroVideo Article x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroVideo Content Package x-teaser 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroVideo Opinion Piece x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" + width={75} + /> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroVideo Package item x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroVideo Paid Post x-teaser 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + + </a> + </div> + <p + className="o-teaser__standfirst" + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroVideo Podcast x-teaser 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "100.0000%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroVideo Top Story x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`@financial-times/x-teaser renders a HeroVideo Video x-teaser 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__video" + > + <div + className="o--if-js" + > + <div + className="o-teaser__image-container js-image-container" + > + <div + className="o-video" + data-o-component="o-video" + data-o-video-autorender="true" + data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" + data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-placeholder="true" + data-o-video-placeholder-hint="Play video" + data-o-video-placeholder-info="[]" + data-o-video-playsinline="true" + /> + </div> + </div> + <div + className="o--if-no-js" + > + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Large Article x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Large Content Package x-teaser 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--large o-teaser--centre o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Large Opinion Piece x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--has-headshot o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" + width={75} + /> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Large Package item x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Large Paid Post x-teaser 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--large o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + + </a> + </div> + <p + className="o-teaser__standfirst" + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Large Podcast x-teaser 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--large o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "100.0000%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Large Top Story x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`@financial-times/x-teaser renders a Large Video x-teaser 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--large o-teaser--has-video js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__video" + > + <div + className="o--if-js" + > + <div + className="o-teaser__image-container js-image-container" + > + <div + className="o-video" + data-o-component="o-video" + data-o-video-autorender="true" + data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" + data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-placeholder="true" + data-o-video-placeholder-hint="Play video" + data-o-video-placeholder-info="[]" + data-o-video-playsinline="true" + /> + </div> + </div> + <div + className="o--if-no-js" + > + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + + </a> + </div> + <p + className="o-teaser__standfirst" + > + The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs + </p> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Small Article x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Small Content Package x-teaser 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--small o-teaser--centre o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Small Opinion Piece x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-headshot o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" + width={75} + /> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Small Package item x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Small Paid Post x-teaser 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--small o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + + </a> + </div> + <p + className="o-teaser__standfirst" + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Small Podcast x-teaser 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--small o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "100.0000%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a Small Top Story x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" + /> + </a> + </div> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`@financial-times/x-teaser renders a Small Video x-teaser 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--small o-teaser--has-video js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__video" + > + <div + className="o--if-js" + > + <div + className="o-teaser__image-container js-image-container" + > + <div + className="o-video" + data-o-component="o-video" + data-o-video-autorender="true" + data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" + data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-placeholder="true" + data-o-video-placeholder-hint="Play video" + data-o-video-placeholder-info="[]" + data-o-video-playsinline="true" + /> + </div> + </div> + <div + className="o--if-no-js" + > + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" + /> + </a> + </div> + </div> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a SmallHeavy Article x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a SmallHeavy Content Package x-teaser 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--small o-teaser--centre o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a SmallHeavy Opinion Piece x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-headshot o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" + width={75} + /> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a SmallHeavy Package item x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a SmallHeavy Paid Post x-teaser 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--small o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + + </a> + </div> + <p + className="o-teaser__standfirst" + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a SmallHeavy Podcast x-teaser 1`] = ` +<div + className="o-teaser o-teaser--audio o-teaser--small o-teaser--has-image js-teaser" + data-id="d1246074-f7d3-4aaf-951c-80a6db495765" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Tech Tonic podcast + </a> + <span + className="o-teaser__tag-suffix" + > + 12 mins + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + > + Who sets the internet standards? + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Hannah Kuchler talks to American social scientist and cyber security expert Andrea… + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "100.0000%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a SmallHeavy Top Story x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--landscape o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + <ul + className="o-teaser__related" + > + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + Removing the fig leaf of charity + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--article" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + A dinner that demeaned both women and men + </a> + </li> + <li + className="o-teaser__related-item o-teaser__related-item--video" + data-content-id="" + > + <a + data-trackable="related" + href="#" + > + PM speaks out after Presidents Club dinner + </a> + </li> + </ul> +</div> +`; + +exports[`@financial-times/x-teaser renders a SmallHeavy Video x-teaser 1`] = ` +<div + className="o-teaser o-teaser--video o-teaser--small o-teaser--has-video js-teaser" + data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Global Trade + </a> + <span + className="o-teaser__tag-suffix" + > + 02:51min + </span> + </div> + </div> + <div + className="o-teaser__video" + > + <div + className="o--if-js" + > + <div + className="o-teaser__image-container js-image-container" + > + <div + className="o-video" + data-o-component="o-video" + data-o-video-autorender="true" + data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" + data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" + data-o-video-placeholder="true" + data-o-video-placeholder-hint="Play video" + data-o-video-placeholder-info="[]" + data-o-video-playsinline="true" + /> + </div> + </div> + <div + className="o--if-no-js" + > + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + FT View: Donald Trump, man of steel + + </a> + </div> + <p + className="o-teaser__standfirst" + > + The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs + </p> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a TopStory Article x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + + </a> + </div> + <p + className="o-teaser__standfirst" + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a TopStory Content Package x-teaser 1`] = ` +<div + className="o-teaser o-teaser--package o-teaser--top-story o-teaser--centre o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + FT Magazine + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + The royal wedding + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Prince Harry and Meghan Markle will tie the knot at Windsor Castle + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a TopStory Opinion Piece x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--has-headshot o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Gideon Rachman + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Anti-Semitism and the threat of identity politics + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Today, hatred of Jews is mixed in with fights about Islam and Israel + </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" + width={75} + /> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a TopStory Package item x-teaser 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-tag" + > + <span + className="o-teaser__tag-prefix" + > + FT Series + </span> + <a + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Financial crisis: Are we safer now? + </a> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why so little has changed since the crash + + </a> + </div> + <p + className="o-teaser__standfirst" + > + Martin Wolf on the power of vested interests in today’s rent-extracting economy + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a TopStory Paid Post x-teaser 1`] = ` +<div + className="o-teaser o-teaser--paid-post o-teaser--top-story o-teaser--has-image js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <div + className="o-teaser__meta-promoted" + > + <span + className="o-teaser__promoted-prefix" + > + Paid post + </span> + <span + className="o-teaser__promoted-by" + > + by UBS + </span> + </div> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Why eSports companies are on a winning streak + + </a> + </div> + <p + className="o-teaser__standfirst" + > + ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2857%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tab-index="-1" + > + <img + alt="" + className="o-teaser__image" + src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" + /> + </a> + </div> + </div> +</div> +`; + +exports[`@financial-times/x-teaser renders a TopStory Podcast x-teaser 1`] = ` <div className="GiftArticle_container__nGwU_" > diff --git a/components/x-follow-button/.bowerrc b/components/x-follow-button/.bowerrc new file mode 100644 index 000000000..59e9a5925 --- /dev/null +++ b/components/x-follow-button/.bowerrc @@ -0,0 +1,8 @@ +{ + "registry": { + "search": [ + "https://origami-bower-registry.ft.com", + "https://registry.bower.io" + ] + } +} diff --git a/components/x-follow-button/bower.json b/components/x-follow-button/bower.json new file mode 100644 index 000000000..21c72aba2 --- /dev/null +++ b/components/x-follow-button/bower.json @@ -0,0 +1,9 @@ +{ + "name": "x-follow-button", + "private": true, + "dependencies": { + "o-colors": "^4.4.1", + "o-icons": "^5.7.1", + "o-typography": "^5.7.4" + } +} diff --git a/components/x-follow-button/package.json b/components/x-follow-button/package.json new file mode 100644 index 000000000..598d21404 --- /dev/null +++ b/components/x-follow-button/package.json @@ -0,0 +1,31 @@ +{ + "name": "@financial-times/x-follow-button", + "version": "1.0.0", + "description": "", + "main": "dist/FollowButton.cjs.js", + "style": "dist/FollowButton.css", + "browser": "dist/FollowButton.es5.js", + "module": "dist/FollowButton.esm.js", + "scripts": { + "prepare": "npm run build", + "build": "node rollup.js", + "start": "node rollup.js --watch", + "postinstall": "bower install" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@financial-times/x-rollup": "file:../../packages/x-rollup", + "rollup": "^0.57.1", + "bower": "^1.7.9", + "node-sass": "^3.8.0" + }, + "peerDependencies": { + "@financial-times/x-engine": "file:../../packages/x-engine" + }, + "dependencies": { + "@financial-times/x-interaction": "file:../x-interaction", + "@financial-times/x-engine": "file:../../packages/x-engine" + } +} diff --git a/components/x-follow-button/readme.md b/components/x-follow-button/readme.md new file mode 100644 index 000000000..8b0cf493f --- /dev/null +++ b/components/x-follow-button/readme.md @@ -0,0 +1,107 @@ +# x-follow-button + +This module provides a template for myFT follow button component. + +(and will potentially eventually replace `{{> n-myft-ui/components/follow-button/follow-button}}`) + +## Installation + +```bash +npm install --save @financial-times/x-follow-button +``` + +[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine + +## Knobs + +- Text (allows to play with the text on the button, and text inside of accompanied HTML) +- Extra Classes (they won't change anything in the storybook, but you can explore how your component's structure would be affected) +- Flags (what would change if you change the flag) +- Status (is the button selected or not) + +## Usage + +Component provided by this module expects a map of [follow button properties](#properties). They can be used with vanilla JavaScript or JSX (if you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: + +```jsx +import React from 'react'; +import { FollowButton } from '@financial-times/x-follow-button'; + +// A == B == C +const a = FollowButton(props); +const b = <FollowButton {...props} />; +const c = React.createElement(FollowButton, props); +``` + +All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. + +[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ + +### Example: how to use with handlebars +(step-by-step guide) + +1. Install `x-hadlebars` +``` +$ npm install -S @financial-times/x-hadlebars +``` + +2. Add `x-hadlebars` to helpers of `n-ui` express app +``` +const xHandlebars = require('@financial-times/x-handlebars'); + +const app = express({ + helpers: { + x: xHandlebars() + } +}); +``` + +3. Add `x-follow-button` to `package.json` +``` +$ npm install -S @financial-times/x-follow-button +``` +or add `"@financial-times/x-follow-button": "file:../x-dash/components/x-follow-button"` if you want to link it to your local version (note that the path is a path to your local `x-dash` folder) + +4. Specify to `package.json` that you are using "hyperons" engine +``` +"x-dash": { + "engine": { + "server": "hyperons" + } +} +``` + +5. Add your button to wherever you want to use it; this is a very basic example, feel free to play with other props +``` +{{{ + x local="../x-dash/components/x-follow-button" + component="FollowButton" + conceptId="{conceptId}" +}}} +``` + +6. Add `x-dash` CSS used by this component +``` +@import "x-follow-button/dist/FollowButton"; +``` + +And all is ready to go! + +Note: we assume that client side JavaScript is handled separately + +## List of all properties (props) + +(Some of the properties don't influence the way button looks or acts, but can be used for e.g. client-side Javascript in the apps). + +Feature | Type | Default value | Knob +--------------------------|---------|---------------------------|------ +`altButtonText` | String | 'Added' | yes +`buttonText` | String | 'Add to myFT' | yes +`conceptId` | String | '00000-0000-00000-00000' | +`csrfToken` | String | 'testTokenValue' | +`extraButtonClasses` | String | null | yes +`followPlusDigestEmail` | Boolean | true | yes +`isSelected` | Boolean | false | yes +`name` | String | 'Test Name' | yes +`switchFollowDigestEmail` | Boolean | false | +`variant` | String | null | yes diff --git a/components/x-follow-button/rollup.js b/components/x-follow-button/rollup.js new file mode 100644 index 000000000..075823328 --- /dev/null +++ b/components/x-follow-button/rollup.js @@ -0,0 +1,4 @@ +const xRollup = require('@financial-times/x-rollup'); +const pkg = require('./package.json'); + +xRollup({ input: './src/FollowButton.jsx', pkg }); diff --git a/components/x-follow-button/src/Button.jsx b/components/x-follow-button/src/Button.jsx new file mode 100644 index 000000000..ab24b5c23 --- /dev/null +++ b/components/x-follow-button/src/Button.jsx @@ -0,0 +1,32 @@ +import { h } from '@financial-times/x-engine'; +import styles from './styles/main.scss'; + +const Button = ({ + conceptId, + followPlusDigestEmail, + isSelected, + variant, + altButtonText, + buttonText, + name, + ...props +}) => ( + <button + data-alternate-text={ isSelected ? (buttonText ? buttonText : `Add ${name} to MyFT`) : (altButtonText ? altButtonText : `Remove ${name} from MyFT`) } + aria-label={ isSelected ? `Remove ${name} from MyFT` : `Add ${name} to MyFT` } + title={ isSelected ? `Remove ${name} from MyFT` : `Add ${name} to MyFT` } + data-alternate-label={ isSelected ? `Add ${name} to MyFT` : `Remove ${name} from MyFT` } + aria-pressed={ isSelected ? 'true' : 'false' } + className={ `${styles['button']} ${styles[`button--${variant}`]}` } + data-concept-id={conceptId} + data-trackable-context-messaging={ + followPlusDigestEmail ? 'add-to-myft-plus-digest-button' : null + } + data-trackable="follow" + type="submit" + { ...props }> + { isSelected ? altButtonText : buttonText } + </button> +); + +export default Button; diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx new file mode 100644 index 000000000..5c998017b --- /dev/null +++ b/components/x-follow-button/src/FollowButton.jsx @@ -0,0 +1,66 @@ +import { h } from '@financial-times/x-engine'; +import { withActions } from '@financial-times/x-interaction'; + +import Button from './Button'; + +const followButtonActions = withActions(() => ({ + onSubmitAction(event) { + event.preventDefault(); + }, + onClickAction() { + return ({isSelected}) => ({ + isSelected: !isSelected + }); + } +})); + +const getFormAction = (conceptId, followPlusDigestEmail, isSelected) => { + if (followPlusDigestEmail) { + return `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put` + } else if (isSelected) { + return `/__myft/api/core/followed/concept/${conceptId}?method=delete` + } else { + return `/__myft/api/core/followed/concept/${conceptId}?method=put` + } +}; + +const BaseFollowButton = ({ + conceptId, + csrfToken, + followPlusDigestEmail, + isSelected, + variant, + altButtonText, + buttonText, + actions, + name +}) => ( + <form + method="GET" + data-myft-ui="follow" + data-concept-id={conceptId} + action={ getFormAction(conceptId, followPlusDigestEmail, isSelected) } + onSubmit={ actions.onSubmitAction } + { ...(followPlusDigestEmail ? { 'data-myft-ui-variant': true } : null) }> + <input value={ csrfToken } + type='hidden' + name='token' + data-myft-csrf-token /> + <Button conceptId={ conceptId } + followPlusDigestEmail={ followPlusDigestEmail } + isSelected={ isSelected } + variant={ variant } + altButtonText={ altButtonText || 'Added' } + buttonText={ buttonText || 'Add to myFT' } + name={ name || 'unnamed' } + onClick={ actions.onClickAction }/> + </form> +); + +const FollowButton = followButtonActions(BaseFollowButton); + +export { + FollowButton, + followButtonActions, + BaseFollowButton, +}; diff --git a/components/x-follow-button/src/styles/components/FollowButton.scss b/components/x-follow-button/src/styles/components/FollowButton.scss new file mode 100644 index 000000000..011f4d216 --- /dev/null +++ b/components/x-follow-button/src/styles/components/FollowButton.scss @@ -0,0 +1,15 @@ +/* Button.scss */ + +@import '../mixins/lozenge/main'; + +/* Mixins: are going to be reused accross various components */ + +.button { + @include myftLozenge($with-toggle-icon: true); +} + +@each $theme in map-keys($myft-lozenge-themes) { + .button--#{$theme} { + @include myftLozengeTheme($theme, $with-toggle-icon: true); + } +} diff --git a/components/x-follow-button/src/styles/main.scss b/components/x-follow-button/src/styles/main.scss new file mode 100644 index 000000000..19b50ddcd --- /dev/null +++ b/components/x-follow-button/src/styles/main.scss @@ -0,0 +1,7 @@ +@import 'o-icons/main'; +@import 'o-colors/main'; +@import 'o-typography/main'; + +@import './mixins/lozenge/main.scss'; + +@import './components/FollowButton.scss'; \ No newline at end of file diff --git a/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss b/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss new file mode 100644 index 000000000..38cfe434a --- /dev/null +++ b/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss @@ -0,0 +1,42 @@ +$theme-map: null; + +$myft-lozenge-themes: ( + standard: ( + background: oColorsGetPaletteColor('claret'), + text: oColorsGetPaletteColor('white'), + highlight: oColorsGetPaletteColor('claret-50'), + pressed-highlight: rgba(oColorsGetPaletteColor('black'), 0.05), + disabled: rgba(oColorsGetPaletteColor('black'), 0.5) + ), + inverse: ( + background: oColorsGetPaletteColor('white'), + text: oColorsGetPaletteColor('claret'), + highlight: rgba(white, 0.8), + pressed-highlight: rgba(white, 0.2), + disabled: rgba(oColorsGetPaletteColor('white'), 0.5) + ), + opinion: ( + background: oColorsGetPaletteColor('oxford-40'), + text: oColorsGetPaletteColor('white'), + highlight: oColorsGetPaletteColor('oxford-30'), + pressed-highlight: rgba(oColorsGetPaletteColor('oxford-40'), 0.2), + disabled: rgba(oColorsGetPaletteColor('black'), 0.5) + ), + monochrome: ( + background: oColorsGetPaletteColor('white'), + text: oColorsGetPaletteColor('black'), + highlight: oColorsGetPaletteColor('white-80'), + pressed-highlight: rgba(oColorsGetPaletteColor('white'), 0.2), + disabled: rgba(oColorsGetPaletteColor('white'), 0.5) + ) +); + +@function getThemeColor($key) { + @return map-get($theme-map, $key); +} + +@mixin withTheme($theme) { + $theme-map: map-get($myft-lozenge-themes, $theme) !global; + + @content; +} \ No newline at end of file diff --git a/components/x-follow-button/src/styles/mixins/lozenge/_toggle-icon.scss b/components/x-follow-button/src/styles/mixins/lozenge/_toggle-icon.scss new file mode 100644 index 000000000..c5e3613f0 --- /dev/null +++ b/components/x-follow-button/src/styles/mixins/lozenge/_toggle-icon.scss @@ -0,0 +1,43 @@ +@mixin getIcon($name, $color) { + @include oIconsGetIcon($icon-name: $name, $container-width: 10, $color: $color, $iconset-version: 1); + content: ''; +} + +@mixin plusIcon($color) { + @include getIcon('plus', $color); + background-size: 25px; + margin: 0 6px -1px 0; +} + +@mixin tickIcon($color) { + @include getIcon('tick', $color); + background-size: 21px; +} + +@mixin myftToggleIcon($theme: standard) { + @include withTheme($theme) { + &::before { + @include plusIcon(getThemeColor(background)); + } + + &[aria-pressed="true"] { + &::before { + @include tickIcon(getThemeColor(text)); + } + } + + &[disabled], + &[disabled]:hover { + background: transparent; + + &::before { + @include plusIcon(getThemeColor(disabled)); + opacity: 0.5; + } + + &[aria-pressed="true"]::before { + @include tickIcon(getThemeColor(disabled)); + } + } + } +} \ No newline at end of file diff --git a/components/x-follow-button/src/styles/mixins/lozenge/main.scss b/components/x-follow-button/src/styles/mixins/lozenge/main.scss new file mode 100644 index 000000000..1a2618244 --- /dev/null +++ b/components/x-follow-button/src/styles/mixins/lozenge/main.scss @@ -0,0 +1,64 @@ +@import './themes'; +@import './toggle-icon'; + +@mixin myftLozengeTheme($theme: standard, $with-toggle-icon: false) { + @if $with-toggle-icon != false { + @include myftToggleIcon($theme); + } + + @include withTheme($theme) { + background-color: transparent; + border: 1px solid getThemeColor(background); + color: getThemeColor(background); + + &:hover, + &:focus { + background-color: getThemeColor(pressed-highlight); + border-color: getThemeColor(background); + color: getThemeColor(background); + } + + &[aria-pressed="true"] { + background-color: getThemeColor(background); + border: 1px solid getThemeColor(background); + color: getThemeColor(text); + + &:hover, + &:focus { + background-color: getThemeColor(highlight); + border-color: getThemeColor(highlight); + color: getThemeColor(text); + } + } + + &[disabled]:hover, + &[disabled] { + background: transparent; + border: 1px solid getThemeColor(disabled); + color: getThemeColor(disabled); + } + } +} + +@mixin myftLozenge($theme: standard, $with-toggle-icon: false) { + @include myftLozengeTheme($theme, $with-toggle-icon); + @include oTypographySansBold($scale: -1); + + border-radius: 100px; // Number that will be larger than any possible height, so that works for all possible button sizes + box-sizing: content-box; + display: block; + font-size: 14px; + margin: 6px 4px 6px 2px; + max-width: 200px; + outline-offset: 2px; + overflow: hidden; + padding: 5px 12px; + text-align: left; + text-overflow: ellipsis; + transition: border-color, background-color 0.5s ease; + white-space: nowrap; + + &:focus { + outline: 2px solid getColor('teal-90'); + } +} \ No newline at end of file diff --git a/components/x-follow-button/stories/follow-button.js b/components/x-follow-button/stories/follow-button.js new file mode 100644 index 000000000..d8861d0b4 --- /dev/null +++ b/components/x-follow-button/stories/follow-button.js @@ -0,0 +1,27 @@ +exports.title = 'Follow Button'; + +const data = { + id: '', + buttonText: 'Add to myFT', + alternateText: 'Added', + extraButtonClasses: null, + variant: null, + switchFollowDigestEmail: false, + followPlusDigestEmail: true, + isSelected: false, + csrfToken: 'testTokenValue', + conceptId: '00000-0000-00000-00000', + name: 'Test Name' +}; + +// This data will provide defaults for the Knobs defined below and used +// to render examples in the documentation site. +exports.data = data; + +// A list of properties to pass to the component when rendered in Storybook. If a Knob +// exists for the property then it will be editable with the default as defined above. +exports.knobs = Object.keys(data); + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module; diff --git a/components/x-follow-button/stories/index.js b/components/x-follow-button/stories/index.js new file mode 100644 index 000000000..9d5205a7a --- /dev/null +++ b/components/x-follow-button/stories/index.js @@ -0,0 +1,14 @@ +const { FollowButton } = require('../'); + +module.exports = { + component: FollowButton, + dependencies: { + 'o-fonts': '^3.0.0', + 'o-typography': '^5.5.0' + }, + stories: [ + require('./follow-button') + ], + knobs: require('./knobs'), + package: require('../package.json') +}; diff --git a/components/x-follow-button/stories/knobs.js b/components/x-follow-button/stories/knobs.js new file mode 100644 index 000000000..8df276159 --- /dev/null +++ b/components/x-follow-button/stories/knobs.js @@ -0,0 +1,42 @@ +// To ensure that component stories do not need to depend on Storybook themselves we return a +// function that may be passed the required dependencies. +module.exports = (data, { text, boolean, selectV2 }) => { + const Groups = { + Text: 'Text', + Status: 'Status', + Flags: 'Flags', + Variant: 'Variant' + }; + + const Text = { + buttonText () { + return text('Default text', data.buttonText, Groups.Text); + }, + altButtonText () { + return text('Alternate text', data.altButtonText, Groups.Text); + }, + name () { + return text('Name of what we add', data.name, Groups.Text); + } + } + + const Status = { + isSelected () { + return boolean('isSelected', data.isSelected, Groups.Status); + } + } + + const Flags = { + followPlusDigestEmail () { + return boolean('followPlusDigestEmail', data.followPlusDigestEmail, Groups.Flags); + } + } + + const Variant = { + variant () { + return selectV2('variant', [ null, 'standard', 'inverse', 'opinion', 'monochrome' ], data.variant, Groups.Variant); + } + } + + return Object.assign({}, Text, Status, Flags, Variant); +}; diff --git a/components/x-styling-demo/.gitignore b/components/x-styling-demo/.gitignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/components/x-styling-demo/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/components/x-styling-demo/rollup.config.js b/components/x-styling-demo/rollup.config.js new file mode 100644 index 000000000..bd90aba60 --- /dev/null +++ b/components/x-styling-demo/rollup.config.js @@ -0,0 +1,6 @@ +import xRollup from '@financial-times/x-rollup'; +import pkg from './package.json'; + +const input = 'src/Button.jsx'; + +export default xRollup({input, pkg}); diff --git a/private/blueprints/component/stories/example.js b/private/blueprints/component/stories/example.js index 6df941bf7..ab8a32f0f 100644 --- a/private/blueprints/component/stories/example.js +++ b/private/blueprints/component/stories/example.js @@ -4,6 +4,8 @@ exports.data = { message: 'Hello World!' }; +exports.knobs = ['count']; + // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; diff --git a/tools/x-workbench/package.json b/tools/x-workbench/package.json new file mode 100644 index 000000000..e69de29bb diff --git a/tools/x-workbench/register-components.js b/tools/x-workbench/register-components.js new file mode 100644 index 000000000..e69de29bb diff --git a/tools/x-workbench/static/components/.gitignore b/tools/x-workbench/static/components/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/tools/x-workbench/static/components/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/web/gatsby-config.js b/web/gatsby-config.js index d75338486..26e347579 100644 --- a/web/gatsby-config.js +++ b/web/gatsby-config.js @@ -13,14 +13,14 @@ module.exports = { options: { name: 'docs', path: './src/data' - }, + } }, { resolve: 'gatsby-source-filesystem', options: { name: 'docs', path: '../docs' - }, + } }, { resolve: 'gatsby-source-filesystem', @@ -30,14 +30,14 @@ module.exports = { // Don't attempt to load any Storybook or source files, as these may // contain syntax and/or features we cannot parse. ignore: [/stories/, /storybook/, /src/] - }, + } }, { resolve: 'gatsby-source-filesystem', options: { name: 'packages', path: '../packages' - }, + } }, // Handles markdown files (creates "MarkdownRemark" nodes) { From 970947893e5b7a93dc61342c2d1bbef6fabcc7cc Mon Sep 17 00:00:00 2001 From: Katerina <katerina.loschinina@ft.com> Date: Fri, 5 Oct 2018 16:39:01 +0100 Subject: [PATCH 266/760] Set main entry in bower.json to enable imports via Bower to resolve. (+1 squashed commit) Squashed commits: [c641ec3b] update snapshot --- .../__snapshots__/snapshots.test.js.snap | 58 +++++++++---------- components/x-follow-button/bower.json | 1 + 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 70f38a348..5989befc3 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -1,40 +1,36 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`@financial-times/x-follow-button renders a default Follow Button x-follow-button 1`] = ` -<div - data-x-dash-id="" +<form + action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" + data-concept-id="00000-0000-00000-00000" + data-myft-ui="follow" + data-myft-ui-variant={true} + method="GET" + onSubmit={[Function]} > - <form - action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" + <input + data-myft-csrf-token={true} + name="token" + type="hidden" + value="testTokenValue" + /> + <button + aria-label="Add Test Name to MyFT" + aria-pressed="false" + className="main_button__3Mk67 undefined" + data-alternate-label="Remove Test Name from MyFT" + data-alternate-text="Added" data-concept-id="00000-0000-00000-00000" - data-myft-ui="follow" - data-myft-ui-variant={true} - method="GET" - onSubmit={[Function]} + data-trackable="follow" + data-trackable-context-messaging="add-to-myft-plus-digest-button" + onClick={[Function]} + title="Add Test Name to MyFT" + type="submit" > - <input - data-myft-csrf-token={true} - name="token" - type="hidden" - value="testTokenValue" - /> - <button - aria-label="Add Test Name to MyFT" - aria-pressed="false" - className="main_button__3Mk67 undefined" - data-alternate-label="Remove Test Name from MyFT" - data-alternate-text="Added" - data-concept-id="00000-0000-00000-00000" - data-trackable="follow" - data-trackable-context-messaging="add-to-myft-plus-digest-button" - onClick={[Function]} - title="Add Test Name to MyFT" - type="submit" - > - Add to myFT - </button> - </form> -</div> + Add to myFT + </button> +</form> `; exports[`@financial-times/x-increment renders a default Async x-increment 1`] = ` diff --git a/components/x-follow-button/bower.json b/components/x-follow-button/bower.json index 21c72aba2..08ca23073 100644 --- a/components/x-follow-button/bower.json +++ b/components/x-follow-button/bower.json @@ -1,6 +1,7 @@ { "name": "x-follow-button", "private": true, + "main": "dist/FollowButton.es5.js", "dependencies": { "o-colors": "^4.4.1", "o-icons": "^5.7.1", From ef56c8ae5d4dfac9f80bf1a9258136a403377226 Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Wed, 7 Nov 2018 16:27:49 +0000 Subject: [PATCH 267/760] Tidy up after rebase. --- tools/x-workbench/package.json | 0 tools/x-workbench/register-components.js | 0 tools/x-workbench/static/components/.gitignore | 2 -- 3 files changed, 2 deletions(-) delete mode 100644 tools/x-workbench/package.json delete mode 100644 tools/x-workbench/register-components.js delete mode 100644 tools/x-workbench/static/components/.gitignore diff --git a/tools/x-workbench/package.json b/tools/x-workbench/package.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/x-workbench/register-components.js b/tools/x-workbench/register-components.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/x-workbench/static/components/.gitignore b/tools/x-workbench/static/components/.gitignore deleted file mode 100644 index d6b7ef32c..000000000 --- a/tools/x-workbench/static/components/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore From 92b229f7ffd71440fafe64adf9e6029ddab6dc64 Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Mon, 12 Nov 2018 10:22:18 +0000 Subject: [PATCH 268/760] Update knobs for Storybook 4 (+1 squashed commit) Squashed commits: [feec44f8] More cleanup after rebase. --- components/x-follow-button/stories/knobs.js | 12 ++++++------ components/x-styling-demo/.gitignore | 1 - components/x-styling-demo/rollup.config.js | 6 ------ private/blueprints/component/stories/example.js | 2 -- 4 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 components/x-styling-demo/.gitignore delete mode 100644 components/x-styling-demo/rollup.config.js diff --git a/components/x-follow-button/stories/knobs.js b/components/x-follow-button/stories/knobs.js index 8df276159..3f01c0dbe 100644 --- a/components/x-follow-button/stories/knobs.js +++ b/components/x-follow-button/stories/knobs.js @@ -1,6 +1,6 @@ // To ensure that component stories do not need to depend on Storybook themselves we return a // function that may be passed the required dependencies. -module.exports = (data, { text, boolean, selectV2 }) => { +module.exports = (data, { text, boolean, select }) => { const Groups = { Text: 'Text', Status: 'Status', @@ -18,25 +18,25 @@ module.exports = (data, { text, boolean, selectV2 }) => { name () { return text('Name of what we add', data.name, Groups.Text); } - } + }; const Status = { isSelected () { return boolean('isSelected', data.isSelected, Groups.Status); } - } + }; const Flags = { followPlusDigestEmail () { return boolean('followPlusDigestEmail', data.followPlusDigestEmail, Groups.Flags); } - } + }; const Variant = { variant () { - return selectV2('variant', [ null, 'standard', 'inverse', 'opinion', 'monochrome' ], data.variant, Groups.Variant); + return select('variant', [ null, 'standard', 'inverse', 'opinion', 'monochrome' ], data.variant, Groups.Variant); } - } + }; return Object.assign({}, Text, Status, Flags, Variant); }; diff --git a/components/x-styling-demo/.gitignore b/components/x-styling-demo/.gitignore deleted file mode 100644 index 53c37a166..000000000 --- a/components/x-styling-demo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist \ No newline at end of file diff --git a/components/x-styling-demo/rollup.config.js b/components/x-styling-demo/rollup.config.js deleted file mode 100644 index bd90aba60..000000000 --- a/components/x-styling-demo/rollup.config.js +++ /dev/null @@ -1,6 +0,0 @@ -import xRollup from '@financial-times/x-rollup'; -import pkg from './package.json'; - -const input = 'src/Button.jsx'; - -export default xRollup({input, pkg}); diff --git a/private/blueprints/component/stories/example.js b/private/blueprints/component/stories/example.js index ab8a32f0f..6df941bf7 100644 --- a/private/blueprints/component/stories/example.js +++ b/private/blueprints/component/stories/example.js @@ -4,8 +4,6 @@ exports.data = { message: 'Hello World!' }; -exports.knobs = ['count']; - // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module; From d9adab30655b1f6f85e2a43018503c2d23053600 Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Wed, 21 Nov 2018 12:09:20 +0000 Subject: [PATCH 269/760] Dispatch custom event on click of button. Expose more meaningful actions. No need for a separate onClick handler. --- .../__snapshots__/snapshots.test.js.snap | 1 - .../x-follow-button/src/FollowButton.jsx | 33 ++++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 5989befc3..1b03ce473 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -24,7 +24,6 @@ exports[`@financial-times/x-follow-button renders a default Follow Button x-foll data-concept-id="00000-0000-00000-00000" data-trackable="follow" data-trackable-context-messaging="add-to-myft-plus-digest-button" - onClick={[Function]} title="Add Test Name to MyFT" type="submit" > diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index 5c998017b..8304ed252 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -3,14 +3,26 @@ import { withActions } from '@financial-times/x-interaction'; import Button from './Button'; -const followButtonActions = withActions(() => ({ - onSubmitAction(event) { - event.preventDefault(); +const followButtonActions = withActions(initialProps => ({ + triggerFollowUnfollow(rootElement) { + return currentProps => { + const detail = { + action: currentProps.isSelected ? 'remove' : 'add', + actorType: 'user', + actorId: null, // myft client sets to user id from session + relationshipName: 'followed', + subjectType: 'concept', + subjectId: initialProps.conceptId, + token: initialProps.csrfToken + }; + rootElement.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })); + }; }, - onClickAction() { - return ({isSelected}) => ({ - isSelected: !isSelected - }); + followed() { + return { isSelected: true }; + }, + unfollowed() { + return { isSelected: false }; } })); @@ -40,7 +52,10 @@ const BaseFollowButton = ({ data-myft-ui="follow" data-concept-id={conceptId} action={ getFormAction(conceptId, followPlusDigestEmail, isSelected) } - onSubmit={ actions.onSubmitAction } + onSubmit={event => { + event.preventDefault(); + actions.triggerFollowUnfollow(event.target); + }} { ...(followPlusDigestEmail ? { 'data-myft-ui-variant': true } : null) }> <input value={ csrfToken } type='hidden' @@ -53,7 +68,7 @@ const BaseFollowButton = ({ altButtonText={ altButtonText || 'Added' } buttonText={ buttonText || 'Add to myFT' } name={ name || 'unnamed' } - onClick={ actions.onClickAction }/> + /> </form> ); From 93873f43104abadeac5d21e22b9643dddeaa2673 Mon Sep 17 00:00:00 2001 From: Dan Searle <dan@interfacer.co.uk> Date: Wed, 21 Nov 2018 15:04:32 +0000 Subject: [PATCH 270/760] Remove data-myft-ui to prevent n-myft-ui generic button handling. (+1 squashed commit) Squashed commits: [4fbe12fc] Add displayName to base component --- __tests__/__snapshots__/snapshots.test.js.snap | 1 - components/x-follow-button/src/FollowButton.jsx | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 1b03ce473..abfbe4c53 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -4,7 +4,6 @@ exports[`@financial-times/x-follow-button renders a default Follow Button x-foll <form action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" data-concept-id="00000-0000-00000-00000" - data-myft-ui="follow" data-myft-ui-variant={true} method="GET" onSubmit={[Function]} diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index 8304ed252..2ea3bd65a 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -15,6 +15,7 @@ const followButtonActions = withActions(initialProps => ({ subjectId: initialProps.conceptId, token: initialProps.csrfToken }; + rootElement.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })); }; }, @@ -49,7 +50,6 @@ const BaseFollowButton = ({ }) => ( <form method="GET" - data-myft-ui="follow" data-concept-id={conceptId} action={ getFormAction(conceptId, followPlusDigestEmail, isSelected) } onSubmit={event => { @@ -72,6 +72,8 @@ const BaseFollowButton = ({ </form> ); +BaseFollowButton.displayName = 'BaseFollowButton'; + const FollowButton = followButtonActions(BaseFollowButton); export { From bfe622cfede220ffcde1b85239c18c4ba9ba0644 Mon Sep 17 00:00:00 2001 From: dan-searle <dan.m.searle@gmail.com> Date: Tue, 27 Nov 2018 15:32:49 +0000 Subject: [PATCH 271/760] Refactor to not use x-interaction. Also simplify the button as it now expects to get rendered by its parent in order to change its state. --- .../__snapshots__/snapshots.test.js.snap | 17 ++- components/x-follow-button/readme.md | 30 ++-- components/x-follow-button/src/Button.jsx | 32 ---- .../x-follow-button/src/FollowButton.jsx | 140 +++++++++--------- .../x-follow-button/stories/follow-button.js | 14 +- components/x-follow-button/stories/knobs.js | 15 +- 6 files changed, 97 insertions(+), 151 deletions(-) delete mode 100644 components/x-follow-button/src/Button.jsx diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index abfbe4c53..b711e3a64 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -15,19 +15,20 @@ exports[`@financial-times/x-follow-button renders a default Follow Button x-foll value="testTokenValue" /> <button - aria-label="Add Test Name to MyFT" + aria-label="Add UK politics & policy to MyFT" aria-pressed="false" - className="main_button__3Mk67 undefined" - data-alternate-label="Remove Test Name from MyFT" - data-alternate-text="Added" + className="main_button__3Mk67 main_button--standard__uaYt1" + dangerouslySetInnerHTML={ + Object { + "__html": "Add UK politics & policy to MyFT", + } + } data-concept-id="00000-0000-00000-00000" data-trackable="follow" data-trackable-context-messaging="add-to-myft-plus-digest-button" - title="Add Test Name to MyFT" + title="Add UK politics & policy to MyFT" type="submit" - > - Add to myFT - </button> + /> </form> `; diff --git a/components/x-follow-button/readme.md b/components/x-follow-button/readme.md index 8b0cf493f..e6d7d937a 100644 --- a/components/x-follow-button/readme.md +++ b/components/x-follow-button/readme.md @@ -12,13 +12,6 @@ npm install --save @financial-times/x-follow-button [engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine -## Knobs - -- Text (allows to play with the text on the button, and text inside of accompanied HTML) -- Extra Classes (they won't change anything in the storybook, but you can explore how your component's structure would be affected) -- Flags (what would change if you change the flag) -- Status (is the button selected or not) - ## Usage Component provided by this module expects a map of [follow button properties](#properties). They can be used with vanilla JavaScript or JSX (if you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: @@ -89,19 +82,16 @@ And all is ready to go! Note: we assume that client side JavaScript is handled separately -## List of all properties (props) +## Props (Some of the properties don't influence the way button looks or acts, but can be used for e.g. client-side Javascript in the apps). -Feature | Type | Default value | Knob ---------------------------|---------|---------------------------|------ -`altButtonText` | String | 'Added' | yes -`buttonText` | String | 'Add to myFT' | yes -`conceptId` | String | '00000-0000-00000-00000' | -`csrfToken` | String | 'testTokenValue' | -`extraButtonClasses` | String | null | yes -`followPlusDigestEmail` | Boolean | true | yes -`isSelected` | Boolean | false | yes -`name` | String | 'Test Name' | yes -`switchFollowDigestEmail` | Boolean | false | -`variant` | String | null | yes +Feature | Type | Required | Default value | Description +--------------------------|---------|----------|----------------|--------------- +`conceptId` | String | yes | none | UUID of the concept +`conceptName` | String | yes | none | Name of the concept +`buttonText` | String | no | "Add ${conceptName} to myFT" or "Remove ${conceptName} from myFT", depending on `isFollowed` | Text to show in the button. +`isFollowed` | Boolean | no | `false` | Whether the concept is followed or not. +`csrfToken` | String | no | none | value included in a hidden form field. +`variant` | String | no | `standard` | One of `standard`, `inverse`, `opinion` or `monochrome`. Other values will be ignored. +`followPlusDigestEmail` | Boolean | no | `false` | Whether following the topic should also subscribe to the digest. diff --git a/components/x-follow-button/src/Button.jsx b/components/x-follow-button/src/Button.jsx deleted file mode 100644 index ab24b5c23..000000000 --- a/components/x-follow-button/src/Button.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { h } from '@financial-times/x-engine'; -import styles from './styles/main.scss'; - -const Button = ({ - conceptId, - followPlusDigestEmail, - isSelected, - variant, - altButtonText, - buttonText, - name, - ...props -}) => ( - <button - data-alternate-text={ isSelected ? (buttonText ? buttonText : `Add ${name} to MyFT`) : (altButtonText ? altButtonText : `Remove ${name} from MyFT`) } - aria-label={ isSelected ? `Remove ${name} from MyFT` : `Add ${name} to MyFT` } - title={ isSelected ? `Remove ${name} from MyFT` : `Add ${name} to MyFT` } - data-alternate-label={ isSelected ? `Add ${name} to MyFT` : `Remove ${name} from MyFT` } - aria-pressed={ isSelected ? 'true' : 'false' } - className={ `${styles['button']} ${styles[`button--${variant}`]}` } - data-concept-id={conceptId} - data-trackable-context-messaging={ - followPlusDigestEmail ? 'add-to-myft-plus-digest-button' : null - } - data-trackable="follow" - type="submit" - { ...props }> - { isSelected ? altButtonText : buttonText } - </button> -); - -export default Button; diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index 2ea3bd65a..beacd946b 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -1,83 +1,75 @@ import { h } from '@financial-times/x-engine'; -import { withActions } from '@financial-times/x-interaction'; +import styles from './styles/main.scss'; -import Button from './Button'; +export const FollowButton = (props) => { + const { + buttonText = null, + conceptId, + conceptName = 'unnamed', + isFollowed, + csrfToken, + followPlusDigestEmail, + variant + } = props; -const followButtonActions = withActions(initialProps => ({ - triggerFollowUnfollow(rootElement) { - return currentProps => { - const detail = { - action: currentProps.isSelected ? 'remove' : 'add', - actorType: 'user', - actorId: null, // myft client sets to user id from session - relationshipName: 'followed', - subjectType: 'concept', - subjectId: initialProps.conceptId, - token: initialProps.csrfToken - }; + const getFormAction = () => { + if (followPlusDigestEmail) { + return `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put` + } else if (isFollowed) { + return `/__myft/api/core/followed/concept/${conceptId}?method=delete` + } else { + return `/__myft/api/core/followed/concept/${conceptId}?method=put` + } + }; - rootElement.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })); - }; - }, - followed() { - return { isSelected: true }; - }, - unfollowed() { - return { isSelected: false }; - } -})); + const followedConceptNameText = `Remove ${conceptName} from MyFT`; + const unfollowedConceptNameText = `Add ${conceptName} to MyFT`; -const getFormAction = (conceptId, followPlusDigestEmail, isSelected) => { - if (followPlusDigestEmail) { - return `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put` - } else if (isSelected) { - return `/__myft/api/core/followed/concept/${conceptId}?method=delete` - } else { - return `/__myft/api/core/followed/concept/${conceptId}?method=put` - } -}; - -const BaseFollowButton = ({ - conceptId, - csrfToken, - followPlusDigestEmail, - isSelected, - variant, - altButtonText, - buttonText, - actions, - name -}) => ( - <form - method="GET" - data-concept-id={conceptId} - action={ getFormAction(conceptId, followPlusDigestEmail, isSelected) } - onSubmit={event => { - event.preventDefault(); - actions.triggerFollowUnfollow(event.target); - }} - { ...(followPlusDigestEmail ? { 'data-myft-ui-variant': true } : null) }> - <input value={ csrfToken } - type='hidden' - name='token' - data-myft-csrf-token /> - <Button conceptId={ conceptId } - followPlusDigestEmail={ followPlusDigestEmail } - isSelected={ isSelected } - variant={ variant } - altButtonText={ altButtonText || 'Added' } - buttonText={ buttonText || 'Add to myFT' } - name={ name || 'unnamed' } - /> - </form> -); + const getButtonText = () => { + if (buttonText) { + return buttonText; + } -BaseFollowButton.displayName = 'BaseFollowButton'; + return isFollowed ? followedConceptNameText : unfollowedConceptNameText; + }; -const FollowButton = followButtonActions(BaseFollowButton); + return ( + <form + method="GET" + data-concept-id={conceptId} + action={getFormAction()} + onSubmit={event => { + event.preventDefault(); + const detail = { + action: isFollowed ? 'remove' : 'add', + actorType: 'user', + actorId: null, // myft client sets to user id from session + relationshipName: 'followed', + subjectType: 'concept', + subjectId: conceptId, + token: csrfToken + }; -export { - FollowButton, - followButtonActions, - BaseFollowButton, + event.target.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })); + }} + {...(followPlusDigestEmail ? { 'data-myft-ui-variant': true } : null)}> + {csrfToken && <input value={csrfToken} + type='hidden' + name='token' + data-myft-csrf-token/>} + <button + title={isFollowed ? followedConceptNameText : unfollowedConceptNameText} + aria-label={isFollowed ? followedConceptNameText : unfollowedConceptNameText} + aria-pressed={isFollowed ? 'true' : 'false'} + className={`${styles['button']} ${styles[`button--${variant}`]}`} + data-concept-id={conceptId} + data-trackable-context-messaging={ + followPlusDigestEmail ? 'add-to-myft-plus-digest-button' : null + } + data-trackable="follow" + type="submit" + dangerouslySetInnerHTML={{__html: getButtonText()}} + /> + </form> + ); }; diff --git a/components/x-follow-button/stories/follow-button.js b/components/x-follow-button/stories/follow-button.js index d8861d0b4..c1f1685db 100644 --- a/components/x-follow-button/stories/follow-button.js +++ b/components/x-follow-button/stories/follow-button.js @@ -1,17 +1,15 @@ exports.title = 'Follow Button'; const data = { + buttonText: '', + conceptId: '00000-0000-00000-00000', + conceptName: 'UK politics & policy', + isFollowed: false, id: '', - buttonText: 'Add to myFT', - alternateText: 'Added', extraButtonClasses: null, - variant: null, - switchFollowDigestEmail: false, + variant: 'standard', followPlusDigestEmail: true, - isSelected: false, - csrfToken: 'testTokenValue', - conceptId: '00000-0000-00000-00000', - name: 'Test Name' + csrfToken: 'testTokenValue' }; // This data will provide defaults for the Knobs defined below and used diff --git a/components/x-follow-button/stories/knobs.js b/components/x-follow-button/stories/knobs.js index 3f01c0dbe..45ec21ecc 100644 --- a/components/x-follow-button/stories/knobs.js +++ b/components/x-follow-button/stories/knobs.js @@ -10,19 +10,16 @@ module.exports = (data, { text, boolean, select }) => { const Text = { buttonText () { - return text('Default text', data.buttonText, Groups.Text); + return text('buttonText', data.buttonText, Groups.Text); }, - altButtonText () { - return text('Alternate text', data.altButtonText, Groups.Text); - }, - name () { - return text('Name of what we add', data.name, Groups.Text); + conceptName () { + return text('Topic name', data.conceptName, Groups.Text); } }; const Status = { - isSelected () { - return boolean('isSelected', data.isSelected, Groups.Status); + isFollowed () { + return boolean('isFollowed', data.isFollowed, Groups.Status); } }; @@ -34,7 +31,7 @@ module.exports = (data, { text, boolean, select }) => { const Variant = { variant () { - return select('variant', [ null, 'standard', 'inverse', 'opinion', 'monochrome' ], data.variant, Groups.Variant); + return select('variant', [ 'standard', 'inverse', 'opinion', 'monochrome' ], data.variant, Groups.Variant); } }; From 3bf840304b2b24b6e69f52451d5cef36ad0f50b2 Mon Sep 17 00:00:00 2001 From: dan-searle <dan.m.searle@gmail.com> Date: Tue, 27 Nov 2018 15:43:39 +0000 Subject: [PATCH 272/760] Use classnames package to apply component classes. Ensure specified variant is supported. --- components/x-follow-button/package.json | 3 ++- components/x-follow-button/src/FollowButton.jsx | 6 +++++- .../x-follow-button/src/styles/components/FollowButton.scss | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/components/x-follow-button/package.json b/components/x-follow-button/package.json index 598d21404..25eff4b7d 100644 --- a/components/x-follow-button/package.json +++ b/components/x-follow-button/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@financial-times/x-interaction": "file:../x-interaction", - "@financial-times/x-engine": "file:../../packages/x-engine" + "@financial-times/x-engine": "file:../../packages/x-engine", + "classnames": "^2.2.6" } } diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index beacd946b..01cc3f819 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -1,4 +1,5 @@ import { h } from '@financial-times/x-engine'; +import classNames from 'classnames'; import styles from './styles/main.scss'; export const FollowButton = (props) => { @@ -11,6 +12,7 @@ export const FollowButton = (props) => { followPlusDigestEmail, variant } = props; + const VARIANTS = ['standard', 'inverse', 'opinion', 'monochrome']; const getFormAction = () => { if (followPlusDigestEmail) { @@ -61,7 +63,9 @@ export const FollowButton = (props) => { title={isFollowed ? followedConceptNameText : unfollowedConceptNameText} aria-label={isFollowed ? followedConceptNameText : unfollowedConceptNameText} aria-pressed={isFollowed ? 'true' : 'false'} - className={`${styles['button']} ${styles[`button--${variant}`]}`} + className={classNames(styles['button'], { + [styles[`button--${variant}`]]: VARIANTS.includes(variant) + })} data-concept-id={conceptId} data-trackable-context-messaging={ followPlusDigestEmail ? 'add-to-myft-plus-digest-button' : null diff --git a/components/x-follow-button/src/styles/components/FollowButton.scss b/components/x-follow-button/src/styles/components/FollowButton.scss index 011f4d216..80062e285 100644 --- a/components/x-follow-button/src/styles/components/FollowButton.scss +++ b/components/x-follow-button/src/styles/components/FollowButton.scss @@ -2,7 +2,7 @@ @import '../mixins/lozenge/main'; -/* Mixins: are going to be reused accross various components */ +/* Mixins: are going to be reused across various components */ .button { @include myftLozenge($with-toggle-icon: true); From 48b3081dd256691612a0e2bd9edc4534d1429d61 Mon Sep 17 00:00:00 2001 From: dan-searle <dan.m.searle@gmail.com> Date: Tue, 27 Nov 2018 17:21:24 +0000 Subject: [PATCH 273/760] Remove x-interaction dependency. --- components/x-follow-button/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/components/x-follow-button/package.json b/components/x-follow-button/package.json index 25eff4b7d..bf74b7f8a 100644 --- a/components/x-follow-button/package.json +++ b/components/x-follow-button/package.json @@ -25,7 +25,6 @@ "@financial-times/x-engine": "file:../../packages/x-engine" }, "dependencies": { - "@financial-times/x-interaction": "file:../x-interaction", "@financial-times/x-engine": "file:../../packages/x-engine", "classnames": "^2.2.6" } From 1175d5acb8e59719896e04c6aa9dece8fbd652c5 Mon Sep 17 00:00:00 2001 From: dan-searle <dan.m.searle@gmail.com> Date: Tue, 4 Dec 2018 15:31:34 +0000 Subject: [PATCH 274/760] Remove focus outline. --- .../x-follow-button/src/styles/mixins/lozenge/main.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-follow-button/src/styles/mixins/lozenge/main.scss b/components/x-follow-button/src/styles/mixins/lozenge/main.scss index 1a2618244..1c20fffe2 100644 --- a/components/x-follow-button/src/styles/mixins/lozenge/main.scss +++ b/components/x-follow-button/src/styles/mixins/lozenge/main.scss @@ -59,6 +59,6 @@ white-space: nowrap; &:focus { - outline: 2px solid getColor('teal-90'); + outline: none; } -} \ No newline at end of file +} From bb517bc9dd278aaded0f1f93c01845fc10751b95 Mon Sep 17 00:00:00 2001 From: dan-searle <dan.m.searle@gmail.com> Date: Wed, 5 Dec 2018 15:04:04 +0000 Subject: [PATCH 275/760] Only run bower install on prepare --- components/x-follow-button/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/x-follow-button/package.json b/components/x-follow-button/package.json index bf74b7f8a..484ac04ab 100644 --- a/components/x-follow-button/package.json +++ b/components/x-follow-button/package.json @@ -7,10 +7,9 @@ "browser": "dist/FollowButton.es5.js", "module": "dist/FollowButton.esm.js", "scripts": { - "prepare": "npm run build", + "prepare": "bower install && npm run build", "build": "node rollup.js", - "start": "node rollup.js --watch", - "postinstall": "bower install" + "start": "node rollup.js --watch" }, "keywords": [], "author": "", From 1c52108c7600592717e6d530cef587d547bcd9f0 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 15 Jan 2019 17:41:28 +0000 Subject: [PATCH 276/760] don't display concept name in button text as a default --- components/x-follow-button/readme.md | 18 ++++++------- .../x-follow-button/src/FollowButton.jsx | 19 +++++++------- components/x-follow-button/stories/index.js | 3 ++- ...{follow-button.js => with-concept-name.js} | 4 +-- .../stories/without-concept-name.js | 25 +++++++++++++++++++ 5 files changed, 47 insertions(+), 22 deletions(-) rename components/x-follow-button/stories/{follow-button.js => with-concept-name.js} (91%) create mode 100644 components/x-follow-button/stories/without-concept-name.js diff --git a/components/x-follow-button/readme.md b/components/x-follow-button/readme.md index e6d7d937a..b34245227 100644 --- a/components/x-follow-button/readme.md +++ b/components/x-follow-button/readme.md @@ -86,12 +86,12 @@ Note: we assume that client side JavaScript is handled separately (Some of the properties don't influence the way button looks or acts, but can be used for e.g. client-side Javascript in the apps). -Feature | Type | Required | Default value | Description ---------------------------|---------|----------|----------------|--------------- -`conceptId` | String | yes | none | UUID of the concept -`conceptName` | String | yes | none | Name of the concept -`buttonText` | String | no | "Add ${conceptName} to myFT" or "Remove ${conceptName} from myFT", depending on `isFollowed` | Text to show in the button. -`isFollowed` | Boolean | no | `false` | Whether the concept is followed or not. -`csrfToken` | String | no | none | value included in a hidden form field. -`variant` | String | no | `standard` | One of `standard`, `inverse`, `opinion` or `monochrome`. Other values will be ignored. -`followPlusDigestEmail` | Boolean | no | `false` | Whether following the topic should also subscribe to the digest. +Feature | Type | Required | Default value | Description +----------------------------|---------|----------|----------------|--------------- +`conceptId` | String | yes | none | UUID of the concept +`conceptName` | String | yes | none | Name of the concept +`useConceptNameAsButtonText`| Boolean | no | `false` | Whether `conceptName` as the button text. +`isFollowed` | Boolean | no | `false` | Whether the concept is followed or not. +`csrfToken` | String | no | none | value included in a hidden form field. +`variant` | String | no | `standard` | One of `standard`, `inverse`, `opinion` or `monochrome`. Other values will be ignored. +`followPlusDigestEmail` | Boolean | no | `false` | Whether following the topic should also subscribe to the digest. diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index 01cc3f819..f43d718c5 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -4,9 +4,9 @@ import styles from './styles/main.scss'; export const FollowButton = (props) => { const { - buttonText = null, + useConceptNameAsButtonText = false, conceptId, - conceptName = 'unnamed', + conceptName, isFollowed, csrfToken, followPlusDigestEmail, @@ -24,17 +24,16 @@ export const FollowButton = (props) => { } }; - const followedConceptNameText = `Remove ${conceptName} from MyFT`; - const unfollowedConceptNameText = `Add ${conceptName} to MyFT`; - const getButtonText = () => { - if (buttonText) { - return buttonText; + if (useConceptNameAsButtonText) { + return conceptName; } - return isFollowed ? followedConceptNameText : unfollowedConceptNameText; + return isFollowed ? `Remove from MyFT` : `Add to MyFT`; }; + const getAccessibleText = () => isFollowed ? `Remove ${conceptName} from MyFT` : `Add ${conceptName} to MyFT`; + return ( <form method="GET" @@ -60,8 +59,8 @@ export const FollowButton = (props) => { name='token' data-myft-csrf-token/>} <button - title={isFollowed ? followedConceptNameText : unfollowedConceptNameText} - aria-label={isFollowed ? followedConceptNameText : unfollowedConceptNameText} + title={getAccessibleText()} + aria-label={getAccessibleText()} aria-pressed={isFollowed ? 'true' : 'false'} className={classNames(styles['button'], { [styles[`button--${variant}`]]: VARIANTS.includes(variant) diff --git a/components/x-follow-button/stories/index.js b/components/x-follow-button/stories/index.js index 9d5205a7a..c3eaa62f2 100644 --- a/components/x-follow-button/stories/index.js +++ b/components/x-follow-button/stories/index.js @@ -7,7 +7,8 @@ module.exports = { 'o-typography': '^5.5.0' }, stories: [ - require('./follow-button') + require('./with-concept-name'), + require('./without-concept-name') ], knobs: require('./knobs'), package: require('../package.json') diff --git a/components/x-follow-button/stories/follow-button.js b/components/x-follow-button/stories/with-concept-name.js similarity index 91% rename from components/x-follow-button/stories/follow-button.js rename to components/x-follow-button/stories/with-concept-name.js index c1f1685db..166c5f607 100644 --- a/components/x-follow-button/stories/follow-button.js +++ b/components/x-follow-button/stories/with-concept-name.js @@ -1,7 +1,7 @@ -exports.title = 'Follow Button'; +exports.title = 'With Concept Name'; const data = { - buttonText: '', + useConceptNameAsButtonText: true, conceptId: '00000-0000-00000-00000', conceptName: 'UK politics & policy', isFollowed: false, diff --git a/components/x-follow-button/stories/without-concept-name.js b/components/x-follow-button/stories/without-concept-name.js new file mode 100644 index 000000000..f527ea2e3 --- /dev/null +++ b/components/x-follow-button/stories/without-concept-name.js @@ -0,0 +1,25 @@ +exports.title = 'Without Concept Name'; + +const data = { + useConceptNameAsButtonText: false, + conceptId: '00000-0000-00000-00000', + conceptName: 'UK politics & policy', + isFollowed: false, + id: '', + extraButtonClasses: null, + variant: 'standard', + followPlusDigestEmail: true, + csrfToken: 'testTokenValue' +}; + +// This data will provide defaults for the Knobs defined below and used +// to render examples in the documentation site. +exports.data = data; + +// A list of properties to pass to the component when rendered in Storybook. If a Knob +// exists for the property then it will be editable with the default as defined above. +exports.knobs = Object.keys(data); + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module; From 6d7de7c5faa7b6c71f6fce5b48796938a58a244a Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 15 Jan 2019 17:57:55 +0000 Subject: [PATCH 277/760] update snapshot tests --- .../__snapshots__/snapshots.test.js.snap | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index b711e3a64..b33eba1b4 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`@financial-times/x-follow-button renders a default Follow Button x-follow-button 1`] = ` +exports[`@financial-times/x-follow-button renders a default With Concept Name x-follow-button 1`] = ` <form action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" data-concept-id="00000-0000-00000-00000" @@ -20,7 +20,39 @@ exports[`@financial-times/x-follow-button renders a default Follow Button x-foll className="main_button__3Mk67 main_button--standard__uaYt1" dangerouslySetInnerHTML={ Object { - "__html": "Add UK politics & policy to MyFT", + "__html": "UK politics & policy", + } + } + data-concept-id="00000-0000-00000-00000" + data-trackable="follow" + data-trackable-context-messaging="add-to-myft-plus-digest-button" + title="Add UK politics & policy to MyFT" + type="submit" + /> +</form> +`; + +exports[`@financial-times/x-follow-button renders a default Without Concept Name x-follow-button 1`] = ` +<form + action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" + data-concept-id="00000-0000-00000-00000" + data-myft-ui-variant={true} + method="GET" + onSubmit={[Function]} +> + <input + data-myft-csrf-token={true} + name="token" + type="hidden" + value="testTokenValue" + /> + <button + aria-label="Add UK politics & policy to MyFT" + aria-pressed="false" + className="main_button__3Mk67 main_button--standard__uaYt1" + dangerouslySetInnerHTML={ + Object { + "__html": "Add to MyFT", } } data-concept-id="00000-0000-00000-00000" From dbbb615b0cb1dfdc817aa15e66ee2a8ce935d8ed Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Tue, 15 Jan 2019 18:24:11 +0000 Subject: [PATCH 278/760] update node-sass --- components/x-follow-button/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-follow-button/package.json b/components/x-follow-button/package.json index 484ac04ab..fdb286668 100644 --- a/components/x-follow-button/package.json +++ b/components/x-follow-button/package.json @@ -18,7 +18,7 @@ "@financial-times/x-rollup": "file:../../packages/x-rollup", "rollup": "^0.57.1", "bower": "^1.7.9", - "node-sass": "^3.8.0" + "node-sass": "^4.9.2" }, "peerDependencies": { "@financial-times/x-engine": "file:../../packages/x-engine" From b1ab83e6f0282aeb1e1151b196acced40c63a973 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 16 Jan 2019 10:49:35 +0000 Subject: [PATCH 279/760] useConceptNameAsButtonText => conceptNameAsButtonText --- components/x-follow-button/readme.md | 2 +- components/x-follow-button/src/FollowButton.jsx | 4 ++-- components/x-follow-button/stories/with-concept-name.js | 2 +- components/x-follow-button/stories/without-concept-name.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/x-follow-button/readme.md b/components/x-follow-button/readme.md index b34245227..7ee14411c 100644 --- a/components/x-follow-button/readme.md +++ b/components/x-follow-button/readme.md @@ -90,7 +90,7 @@ Feature | Type | Required | Default value | Description ----------------------------|---------|----------|----------------|--------------- `conceptId` | String | yes | none | UUID of the concept `conceptName` | String | yes | none | Name of the concept -`useConceptNameAsButtonText`| Boolean | no | `false` | Whether `conceptName` as the button text. +`conceptNameAsButtonText`| Boolean | no | `false` | Whether `conceptName` as the button text. `isFollowed` | Boolean | no | `false` | Whether the concept is followed or not. `csrfToken` | String | no | none | value included in a hidden form field. `variant` | String | no | `standard` | One of `standard`, `inverse`, `opinion` or `monochrome`. Other values will be ignored. diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index f43d718c5..0364eedaf 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -4,7 +4,7 @@ import styles from './styles/main.scss'; export const FollowButton = (props) => { const { - useConceptNameAsButtonText = false, + conceptNameAsButtonText = false, conceptId, conceptName, isFollowed, @@ -25,7 +25,7 @@ export const FollowButton = (props) => { }; const getButtonText = () => { - if (useConceptNameAsButtonText) { + if (conceptNameAsButtonText) { return conceptName; } diff --git a/components/x-follow-button/stories/with-concept-name.js b/components/x-follow-button/stories/with-concept-name.js index 166c5f607..9e93728da 100644 --- a/components/x-follow-button/stories/with-concept-name.js +++ b/components/x-follow-button/stories/with-concept-name.js @@ -1,7 +1,7 @@ exports.title = 'With Concept Name'; const data = { - useConceptNameAsButtonText: true, + conceptNameAsButtonText: true, conceptId: '00000-0000-00000-00000', conceptName: 'UK politics & policy', isFollowed: false, diff --git a/components/x-follow-button/stories/without-concept-name.js b/components/x-follow-button/stories/without-concept-name.js index f527ea2e3..d03f01cb3 100644 --- a/components/x-follow-button/stories/without-concept-name.js +++ b/components/x-follow-button/stories/without-concept-name.js @@ -1,7 +1,7 @@ exports.title = 'Without Concept Name'; const data = { - useConceptNameAsButtonText: false, + conceptNameAsButtonText: false, conceptId: '00000-0000-00000-00000', conceptName: 'UK politics & policy', isFollowed: false, From acc728c37e7f56de26a118f7fcffee397619de32 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 16 Jan 2019 10:51:25 +0000 Subject: [PATCH 280/760] modify explanation of conceptNameAsButtonText --- components/x-follow-button/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-follow-button/readme.md b/components/x-follow-button/readme.md index 7ee14411c..e0406b171 100644 --- a/components/x-follow-button/readme.md +++ b/components/x-follow-button/readme.md @@ -90,7 +90,7 @@ Feature | Type | Required | Default value | Description ----------------------------|---------|----------|----------------|--------------- `conceptId` | String | yes | none | UUID of the concept `conceptName` | String | yes | none | Name of the concept -`conceptNameAsButtonText`| Boolean | no | `false` | Whether `conceptName` as the button text. +`conceptNameAsButtonText` | Boolean | no | `false` | If true will use the concept name as the button text, otherwise will default to "Add to MyFT" or "Remove from MyFT" (depending on isFollowed prop). `isFollowed` | Boolean | no | `false` | Whether the concept is followed or not. `csrfToken` | String | no | none | value included in a hidden form field. `variant` | String | no | `standard` | One of `standard`, `inverse`, `opinion` or `monochrome`. Other values will be ignored. From 8427d7cf0d57f42f08e4dcd69e79b67219170da0 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 16 Jan 2019 11:00:51 +0000 Subject: [PATCH 281/760] conceptNameAsButtonText as a knob not a separated story --- ...thout-concept-name.js => follow-button.js} | 2 +- components/x-follow-button/stories/index.js | 3 +-- components/x-follow-button/stories/knobs.js | 4 +-- .../stories/with-concept-name.js | 25 ------------------- 4 files changed, 4 insertions(+), 30 deletions(-) rename components/x-follow-button/stories/{without-concept-name.js => follow-button.js} (95%) delete mode 100644 components/x-follow-button/stories/with-concept-name.js diff --git a/components/x-follow-button/stories/without-concept-name.js b/components/x-follow-button/stories/follow-button.js similarity index 95% rename from components/x-follow-button/stories/without-concept-name.js rename to components/x-follow-button/stories/follow-button.js index d03f01cb3..7dd1e8309 100644 --- a/components/x-follow-button/stories/without-concept-name.js +++ b/components/x-follow-button/stories/follow-button.js @@ -1,4 +1,4 @@ -exports.title = 'Without Concept Name'; +exports.title = 'Follow Button'; const data = { conceptNameAsButtonText: false, diff --git a/components/x-follow-button/stories/index.js b/components/x-follow-button/stories/index.js index c3eaa62f2..9d5205a7a 100644 --- a/components/x-follow-button/stories/index.js +++ b/components/x-follow-button/stories/index.js @@ -7,8 +7,7 @@ module.exports = { 'o-typography': '^5.5.0' }, stories: [ - require('./with-concept-name'), - require('./without-concept-name') + require('./follow-button') ], knobs: require('./knobs'), package: require('../package.json') diff --git a/components/x-follow-button/stories/knobs.js b/components/x-follow-button/stories/knobs.js index 45ec21ecc..f9f115a2e 100644 --- a/components/x-follow-button/stories/knobs.js +++ b/components/x-follow-button/stories/knobs.js @@ -9,8 +9,8 @@ module.exports = (data, { text, boolean, select }) => { }; const Text = { - buttonText () { - return text('buttonText', data.buttonText, Groups.Text); + conceptNameAsButtonText () { + return boolean('conceptNameAsButtonText', data.conceptNameAsButtonText, Groups.Flags); }, conceptName () { return text('Topic name', data.conceptName, Groups.Text); diff --git a/components/x-follow-button/stories/with-concept-name.js b/components/x-follow-button/stories/with-concept-name.js deleted file mode 100644 index 9e93728da..000000000 --- a/components/x-follow-button/stories/with-concept-name.js +++ /dev/null @@ -1,25 +0,0 @@ -exports.title = 'With Concept Name'; - -const data = { - conceptNameAsButtonText: true, - conceptId: '00000-0000-00000-00000', - conceptName: 'UK politics & policy', - isFollowed: false, - id: '', - extraButtonClasses: null, - variant: 'standard', - followPlusDigestEmail: true, - csrfToken: 'testTokenValue' -}; - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = data; - -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = Object.keys(data); - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; From 165f04c7a1396557acb840f14b957ff6834b1f6a Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 16 Jan 2019 11:06:21 +0000 Subject: [PATCH 282/760] update snapshot tests --- .../__snapshots__/snapshots.test.js.snap | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index b33eba1b4..bb6d9693e 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -1,38 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`@financial-times/x-follow-button renders a default With Concept Name x-follow-button 1`] = ` -<form - action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" - data-concept-id="00000-0000-00000-00000" - data-myft-ui-variant={true} - method="GET" - onSubmit={[Function]} -> - <input - data-myft-csrf-token={true} - name="token" - type="hidden" - value="testTokenValue" - /> - <button - aria-label="Add UK politics & policy to MyFT" - aria-pressed="false" - className="main_button__3Mk67 main_button--standard__uaYt1" - dangerouslySetInnerHTML={ - Object { - "__html": "UK politics & policy", - } - } - data-concept-id="00000-0000-00000-00000" - data-trackable="follow" - data-trackable-context-messaging="add-to-myft-plus-digest-button" - title="Add UK politics & policy to MyFT" - type="submit" - /> -</form> -`; - -exports[`@financial-times/x-follow-button renders a default Without Concept Name x-follow-button 1`] = ` +exports[`@financial-times/x-follow-button renders a default Follow Button x-follow-button 1`] = ` <form action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" data-concept-id="00000-0000-00000-00000" From fcdf4054b94ecac9b0947993770b6e4f6964b828 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 16 Jan 2019 14:06:18 +0000 Subject: [PATCH 283/760] eslintignore tools/x-docs/static/** --- .eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 25a743b72..52079d084 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,4 +6,4 @@ **/public/** **/public-prod/** **/blueprints/** -web/static/** \ No newline at end of file +web/static/** From 493414beec1e0101347ca29ed01512c3f06eb51b Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 16 Jan 2019 15:37:54 +0000 Subject: [PATCH 284/760] MyFT => myFT --- components/x-follow-button/src/FollowButton.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index 0364eedaf..11f3f1750 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -29,10 +29,10 @@ export const FollowButton = (props) => { return conceptName; } - return isFollowed ? `Remove from MyFT` : `Add to MyFT`; + return isFollowed ? `Remove from myFT` : `Add to myFT`; }; - const getAccessibleText = () => isFollowed ? `Remove ${conceptName} from MyFT` : `Add ${conceptName} to MyFT`; + const getAccessibleText = () => isFollowed ? `Remove ${conceptName} from myFT` : `Add ${conceptName} to myFT`; return ( <form From 35fe36cd6916bef409c91de267e5ee4f4de8ed3e Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Wed, 16 Jan 2019 15:44:01 +0000 Subject: [PATCH 285/760] update snapshots --- __tests__/__snapshots__/snapshots.test.js.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index bb6d9693e..bb030e471 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -15,18 +15,18 @@ exports[`@financial-times/x-follow-button renders a default Follow Button x-foll value="testTokenValue" /> <button - aria-label="Add UK politics & policy to MyFT" + aria-label="Add UK politics & policy to myFT" aria-pressed="false" className="main_button__3Mk67 main_button--standard__uaYt1" dangerouslySetInnerHTML={ Object { - "__html": "Add to MyFT", + "__html": "Add to myFT", } } data-concept-id="00000-0000-00000-00000" data-trackable="follow" data-trackable-context-messaging="add-to-myft-plus-digest-button" - title="Add UK politics & policy to MyFT" + title="Add UK politics & policy to myFT" type="submit" /> </form> From 651eb753599303415b5154ca1867468ce33212a5 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Fri, 18 Jan 2019 09:42:07 +0000 Subject: [PATCH 286/760] add subjectName in event.detail --- components/x-follow-button/src/FollowButton.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index 11f3f1750..c15f90f71 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -48,6 +48,7 @@ export const FollowButton = (props) => { relationshipName: 'followed', subjectType: 'concept', subjectId: conceptId, + subjectName: conceptName, token: csrfToken }; From 7cfb279037a8cbf92f1515f26c72170cf84f0cf6 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Mon, 21 Jan 2019 10:22:38 +0000 Subject: [PATCH 287/760] revert the commit to add subjectName in event.detail --- components/x-follow-button/src/FollowButton.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index c15f90f71..11f3f1750 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -48,7 +48,6 @@ export const FollowButton = (props) => { relationshipName: 'followed', subjectType: 'concept', subjectId: conceptId, - subjectName: conceptName, token: csrfToken }; From 86ac88321c0783582e2f6558485da089976d3142 Mon Sep 17 00:00:00 2001 From: fenglish <asuka.ochi@ft.com> Date: Thu, 24 Jan 2019 12:35:00 +0000 Subject: [PATCH 288/760] Modify the followed button text --- components/x-follow-button/src/FollowButton.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index 11f3f1750..a77066051 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -29,7 +29,7 @@ export const FollowButton = (props) => { return conceptName; } - return isFollowed ? `Remove from myFT` : `Add to myFT`; + return isFollowed ? `Added` : `Add to myFT`; }; const getAccessibleText = () => isFollowed ? `Remove ${conceptName} from myFT` : `Add ${conceptName} to myFT`; From 240650f856a5d22275e1dee1d77f40824ce83513 Mon Sep 17 00:00:00 2001 From: Dan Searle <dan-searle@users.noreply.github.com> Date: Thu, 24 Jan 2019 13:09:48 +0000 Subject: [PATCH 289/760] Remove CSS comments ...as they cause issues when other components import these styles using CSS modules :global --- .../x-follow-button/src/styles/components/FollowButton.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/x-follow-button/src/styles/components/FollowButton.scss b/components/x-follow-button/src/styles/components/FollowButton.scss index 80062e285..3c98e9db9 100644 --- a/components/x-follow-button/src/styles/components/FollowButton.scss +++ b/components/x-follow-button/src/styles/components/FollowButton.scss @@ -1,9 +1,5 @@ -/* Button.scss */ - @import '../mixins/lozenge/main'; -/* Mixins: are going to be reused across various components */ - .button { @include myftLozenge($with-toggle-icon: true); } From b6e78320b5f4f3d0e470af74e0298b0d503f227c Mon Sep 17 00:00:00 2001 From: dan-searle <dan.m.searle@gmail.com> Date: Wed, 6 Feb 2019 11:19:20 +0000 Subject: [PATCH 290/760] More useful readme --- components/x-follow-button/readme.md | 98 +++++++--------------------- 1 file changed, 22 insertions(+), 76 deletions(-) diff --git a/components/x-follow-button/readme.md b/components/x-follow-button/readme.md index e0406b171..e1ce947ff 100644 --- a/components/x-follow-button/readme.md +++ b/components/x-follow-button/readme.md @@ -1,8 +1,6 @@ # x-follow-button -This module provides a template for myFT follow button component. - -(and will potentially eventually replace `{{> n-myft-ui/components/follow-button/follow-button}}`) +This module provides a template for myFT follow topic button, and is intended to replace the legacy handlebars component in [n-myft-ui](https://github.com/Financial-Times/n-myft-ui/tree/master/components/follow-button). ## Installation @@ -10,78 +8,6 @@ This module provides a template for myFT follow button component. npm install --save @financial-times/x-follow-button ``` -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine - -## Usage - -Component provided by this module expects a map of [follow button properties](#properties). They can be used with vanilla JavaScript or JSX (if you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: - -```jsx -import React from 'react'; -import { FollowButton } from '@financial-times/x-follow-button'; - -// A == B == C -const a = FollowButton(props); -const b = <FollowButton {...props} />; -const c = React.createElement(FollowButton, props); -``` - -All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. - -[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ - -### Example: how to use with handlebars -(step-by-step guide) - -1. Install `x-hadlebars` -``` -$ npm install -S @financial-times/x-hadlebars -``` - -2. Add `x-hadlebars` to helpers of `n-ui` express app -``` -const xHandlebars = require('@financial-times/x-handlebars'); - -const app = express({ - helpers: { - x: xHandlebars() - } -}); -``` - -3. Add `x-follow-button` to `package.json` -``` -$ npm install -S @financial-times/x-follow-button -``` -or add `"@financial-times/x-follow-button": "file:../x-dash/components/x-follow-button"` if you want to link it to your local version (note that the path is a path to your local `x-dash` folder) - -4. Specify to `package.json` that you are using "hyperons" engine -``` -"x-dash": { - "engine": { - "server": "hyperons" - } -} -``` - -5. Add your button to wherever you want to use it; this is a very basic example, feel free to play with other props -``` -{{{ - x local="../x-dash/components/x-follow-button" - component="FollowButton" - conceptId="{conceptId}" -}}} -``` - -6. Add `x-dash` CSS used by this component -``` -@import "x-follow-button/dist/FollowButton"; -``` - -And all is ready to go! - -Note: we assume that client side JavaScript is handled separately - ## Props (Some of the properties don't influence the way button looks or acts, but can be used for e.g. client-side Javascript in the apps). @@ -92,6 +18,26 @@ Feature | Type | Required | Default value | Description `conceptName` | String | yes | none | Name of the concept `conceptNameAsButtonText` | Boolean | no | `false` | If true will use the concept name as the button text, otherwise will default to "Add to MyFT" or "Remove from MyFT" (depending on isFollowed prop). `isFollowed` | Boolean | no | `false` | Whether the concept is followed or not. -`csrfToken` | String | no | none | value included in a hidden form field. +`csrfToken` | String | no | none | CSRF token (will be included in a hidden form field). `variant` | String | no | `standard` | One of `standard`, `inverse`, `opinion` or `monochrome`. Other values will be ignored. `followPlusDigestEmail` | Boolean | no | `false` | Whether following the topic should also subscribe to the digest. + +## Client side behaviour + +For users with JavaScript enabled, the default form submit action is prevented, and a custom event (named 'x-follow-button') will be dispatched on the form element. + +This custom event will contain the following in its `detail` object: + +Property | Value +-------------------|----------------- +`action` | `add` or `remove` +`actorType` | `user` +`relationshipName` | `followed` +`subjectType` | `concept` +`subjectId` | the value of the `conceptId` prop +`token` | the value of the `csrfToken` prop + +It is up to the consumer of this component to listen for the `x-follow-button` event, and use this data, along with the user's ID, and carry out the appropriate action. + +For example, if using `next-myft-client` to carry out the follow/unfollow action, n-myft-ui provides a x-button-interaction component for this: +https://github.com/Financial-Times/n-myft-ui/blob/master/components/x-button-integration/index.js From bfdbe7c4845bcad1cbf199c5e22d3233ea097064 Mon Sep 17 00:00:00 2001 From: dan-searle <dan.m.searle@gmail.com> Date: Wed, 6 Feb 2019 16:00:21 +0000 Subject: [PATCH 291/760] Not enough knobs to make it worth splitting them into groups. --- .../x-follow-button/stories/follow-button.js | 6 +-- components/x-follow-button/stories/knobs.js | 38 +++++-------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/components/x-follow-button/stories/follow-button.js b/components/x-follow-button/stories/follow-button.js index 7dd1e8309..ded6ff6d1 100644 --- a/components/x-follow-button/stories/follow-button.js +++ b/components/x-follow-button/stories/follow-button.js @@ -1,13 +1,11 @@ exports.title = 'Follow Button'; const data = { + isFollowed: false, + variant: 'standard', conceptNameAsButtonText: false, conceptId: '00000-0000-00000-00000', conceptName: 'UK politics & policy', - isFollowed: false, - id: '', - extraButtonClasses: null, - variant: 'standard', followPlusDigestEmail: true, csrfToken: 'testTokenValue' }; diff --git a/components/x-follow-button/stories/knobs.js b/components/x-follow-button/stories/knobs.js index f9f115a2e..331320f73 100644 --- a/components/x-follow-button/stories/knobs.js +++ b/components/x-follow-button/stories/knobs.js @@ -1,39 +1,21 @@ // To ensure that component stories do not need to depend on Storybook themselves we return a // function that may be passed the required dependencies. module.exports = (data, { text, boolean, select }) => { - const Groups = { - Text: 'Text', - Status: 'Status', - Flags: 'Flags', - Variant: 'Variant' - }; - - const Text = { + return { conceptNameAsButtonText () { - return boolean('conceptNameAsButtonText', data.conceptNameAsButtonText, Groups.Flags); + return boolean('conceptNameAsButtonText', data.conceptNameAsButtonText); }, - conceptName () { - return text('Topic name', data.conceptName, Groups.Text); - } - }; - - const Status = { isFollowed () { - return boolean('isFollowed', data.isFollowed, Groups.Status); - } - }; - - const Flags = { + return boolean('isFollowed', data.isFollowed); + }, + conceptName () { + return text('Topic name', data.conceptName); + }, followPlusDigestEmail () { - return boolean('followPlusDigestEmail', data.followPlusDigestEmail, Groups.Flags); - } - }; - - const Variant = { + return boolean('followPlusDigestEmail', data.followPlusDigestEmail); + }, variant () { - return select('variant', [ 'standard', 'inverse', 'opinion', 'monochrome' ], data.variant, Groups.Variant); + return select('variant', [ 'standard', 'inverse', 'opinion', 'monochrome' ], data.variant); } }; - - return Object.assign({}, Text, Status, Flags, Variant); }; From 8eec75d3d0bef535218732bebe8aa247f3d0e728 Mon Sep 17 00:00:00 2001 From: Pavel Pichrt <pavel@heepo.co> Date: Wed, 27 Feb 2019 16:18:15 +0000 Subject: [PATCH 292/760] Added onClick prop to X-Follow-Button --- components/x-follow-button/src/FollowButton.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index a77066051..eb23adb47 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -10,6 +10,7 @@ export const FollowButton = (props) => { isFollowed, csrfToken, followPlusDigestEmail, + onClick, variant } = props; const VARIANTS = ['standard', 'inverse', 'opinion', 'monochrome']; @@ -51,6 +52,10 @@ export const FollowButton = (props) => { token: csrfToken }; + if (typeof onClick === 'function') { + onClick(detail); + } + event.target.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })); }} {...(followPlusDigestEmail ? { 'data-myft-ui-variant': true } : null)}> From 537f1f1affc36b50361b0d40a465e0b5bc58a0be Mon Sep 17 00:00:00 2001 From: Pavel Pichrt <pavel@heepo.co> Date: Thu, 28 Feb 2019 11:46:06 +0000 Subject: [PATCH 293/760] renamed onClick prop to onSubmit --- components/x-follow-button/src/FollowButton.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index eb23adb47..dab4dc9f3 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -10,7 +10,7 @@ export const FollowButton = (props) => { isFollowed, csrfToken, followPlusDigestEmail, - onClick, + onSubmit, variant } = props; const VARIANTS = ['standard', 'inverse', 'opinion', 'monochrome']; @@ -52,8 +52,8 @@ export const FollowButton = (props) => { token: csrfToken }; - if (typeof onClick === 'function') { - onClick(detail); + if (typeof onSubmit === 'function') { + onSubmit(detail); } event.target.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })); From 53c1f5a415fd924ec71ced8ed73fe2c27b3005de Mon Sep 17 00:00:00 2001 From: Katerina Loschinina <ekaterina.loshchinina@gmail.com> Date: Fri, 15 Nov 2019 10:57:31 +0000 Subject: [PATCH 294/760] Add x-follow-button to the new story format Post the update whilst rebasing to master --- .storybook/register-components.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 06ad9288b..eeba6aee4 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -5,6 +5,7 @@ const components = [ require('../components/x-styling-demo/stories'), require('../components/x-gift-article/stories'), require('../components/x-podcast-launchers/stories'), + require('../components/x-follow-button/stories') ]; module.exports = components; From 5f52bcf66da83dfdaa988fdb20d7b9dcf7d6b838 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 19 Nov 2019 17:38:33 +0000 Subject: [PATCH 295/760] Upgrade x-podcast-launchers to o-forms v7 o-forms V7 swaps around some classes. This commit updates the classes so that V7 works. --- components/x-podcast-launchers/readme.md | 2 +- .../src/PodcastLaunchers.jsx | 18 +++++------------- .../x-podcast-launchers/stories/index.js | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/components/x-podcast-launchers/readme.md b/components/x-podcast-launchers/readme.md index 71d038427..29ed07b87 100644 --- a/components/x-podcast-launchers/readme.md +++ b/components/x-podcast-launchers/readme.md @@ -32,7 +32,7 @@ $o-buttons-themes: ( primary: 'primary', ); ``` -[o-forms](https://registry.origami.ft.com/components/o-forms) v6 (v7 above breaks the styling) +[o-forms](https://registry.origami.ft.com/components/o-forms) v7 :memo: Only uses the text input with suffix button style classes ## Usage diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.jsx b/components/x-podcast-launchers/src/PodcastLaunchers.jsx index 95ea8c4a9..96906e1b5 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.jsx +++ b/components/x-podcast-launchers/src/PodcastLaunchers.jsx @@ -18,20 +18,12 @@ const podcastAppLinkStyles = [ ].join(' '); const rssUrlWrapperStyles = [ - 'o-forms__affix-wrapper', + 'o-forms-input', + 'o-forms-input--suffix', + 'o-forms-input--text', styles.rssUrlWrapper ].join(' '); -const rssUrlInputStyles = [ - 'o-forms__text', - styles.rssUrlInput -].join(' '); - -const rssUrlCopyButtonWrapperStyles = [ - 'o-forms__suffix', - styles.rssUrlCopyButton -].join(' '); - const noAppWrapperStyles = [ 'podcast-launchers__no-app-wrapper', styles.noAppWrapper @@ -87,8 +79,8 @@ class PodcastLaunchers extends Component { ))} <li key='Rss Url' className={rssUrlWrapperStyles}> - <input className={rssUrlInputStyles} value={rssUrl} type='text' readOnly/> - <div className={rssUrlCopyButtonWrapperStyles}> + <input className={styles.rssUrlInput} value={rssUrl} type='text' readOnly/> + <div className={styles.rssUrlCopyButton}> <button className={basicButtonStyles} onClick={copyToClipboard} diff --git a/components/x-podcast-launchers/stories/index.js b/components/x-podcast-launchers/stories/index.js index dee2e4ceb..73f036947 100644 --- a/components/x-podcast-launchers/stories/index.js +++ b/components/x-podcast-launchers/stories/index.js @@ -9,7 +9,7 @@ exports.dependencies = { 'o-normalise': '^1.6.0', 'o-typography': '^5.5.0', 'o-buttons': '^5.16.6', - 'o-forms': '^6.0.3' + 'o-forms': '^7.0.0' }; exports.stories = [ From e9338da6d3e869ff7a7c2c92e18a8ccd9fa7dba2 Mon Sep 17 00:00:00 2001 From: Katerina Loschinina <ekaterina.loshchinina@gmail.com> Date: Fri, 15 Nov 2019 10:57:42 +0000 Subject: [PATCH 296/760] Update snapshot --- .../__snapshots__/snapshots.test.js.snap | 4665 +---------------- 1 file changed, 1 insertion(+), 4664 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index bb030e471..5e0cd32f6 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -1,4669 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`@financial-times/x-follow-button renders a default Follow Button x-follow-button 1`] = ` -<form - action="/__myft/api/core/follow-plus-digest-email/00000-0000-00000-00000?method=put" - data-concept-id="00000-0000-00000-00000" - data-myft-ui-variant={true} - method="GET" - onSubmit={[Function]} -> - <input - data-myft-csrf-token={true} - name="token" - type="hidden" - value="testTokenValue" - /> - <button - aria-label="Add UK politics & policy to myFT" - aria-pressed="false" - className="main_button__3Mk67 main_button--standard__uaYt1" - dangerouslySetInnerHTML={ - Object { - "__html": "Add to myFT", - } - } - data-concept-id="00000-0000-00000-00000" - data-trackable="follow" - data-trackable-context-messaging="add-to-myft-plus-digest-button" - title="Add UK politics & policy to myFT" - type="submit" - /> -</form> -`; - -exports[`@financial-times/x-increment renders a default Async x-increment 1`] = ` -<div> - <span> - 1 - </span> - <button - disabled={false} - onClick={[Function]} - > - Increment - </button> -</div> -`; - -exports[`@financial-times/x-increment renders a default Increment x-increment 1`] = ` -<div> - <span> - 1 - </span> - <button - disabled={false} - onClick={[Function]} - > - Increment - </button> -</div> -`; - -exports[`@financial-times/x-styling-demo renders a default Styling x-styling-demo 1`] = ` -<button - className="Button_button__vS7Mv" -> - Click me! -</button> -`; - -exports[`@financial-times/x-teaser renders a Hero Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - - </a> - </div> - <p - className="o-teaser__standfirst" - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "100.0000%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a Hero Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - - </a> - </div> - <p - className="o-teaser__standfirst" - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "100.0000%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroNarrow Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - - </a> - </div> - <p - className="o-teaser__standfirst" - > - The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs - </p> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - - </a> - </div> - <p - className="o-teaser__standfirst" - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "100.0000%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroOverlay Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--hero o-teaser--hero-image o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--hero o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - - </a> - </div> - <p - className="o-teaser__standfirst" - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--hero o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "100.0000%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a HeroVideo Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--hero o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--large o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--large o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--large o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--large o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - - </a> - </div> - <p - className="o-teaser__standfirst" - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--large o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "100.0000%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--large o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a Large Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--large o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - - </a> - </div> - <p - className="o-teaser__standfirst" - > - The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs - </p> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--small o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--small o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - - </a> - </div> - <p - className="o-teaser__standfirst" - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--small o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "100.0000%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=640" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a Small Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--small o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=420" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--small o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--small o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - - </a> - </div> - <p - className="o-teaser__standfirst" - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Podcast x-teaser 1`] = ` -<div - className="o-teaser o-teaser--audio o-teaser--small o-teaser--has-image js-teaser" - data-id="d1246074-f7d3-4aaf-951c-80a6db495765" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Tech Tonic podcast - </a> - <span - className="o-teaser__tag-suffix" - > - 12 mins - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - > - Who sets the internet standards? - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Hannah Kuchler talks to American social scientist and cyber security expert Andrea… - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "100.0000%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="https://www.ft.com/content/d1246074-f7d3-4aaf-951c-80a6db495765" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fthumborcdn.acast.com%2FpKDW6uGdnSFB0lH8iZYb8LAumyk%3D%2F1500x1500%2Fhttps%3A%2F%2Fmediacdn.acast.com%2Fassets%2Fd1246074-f7d3-4aaf-951c-80a6db495765%2Fcover-image-jnm16b8a-tt_limbago_1400.jpg?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Top Story x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--small o-teaser--landscape o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> - <ul - className="o-teaser__related" - > - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - Removing the fig leaf of charity - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--article" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - A dinner that demeaned both women and men - </a> - </li> - <li - className="o-teaser__related-item o-teaser__related-item--video" - data-content-id="" - > - <a - data-trackable="related" - href="#" - > - PM speaks out after Presidents Club dinner - </a> - </li> - </ul> -</div> -`; - -exports[`@financial-times/x-teaser renders a SmallHeavy Video x-teaser 1`] = ` -<div - className="o-teaser o-teaser--video o-teaser--small o-teaser--has-video js-teaser" - data-id="0e89d872-5711-457b-80b1-4ca0d8afea46" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Global Trade - </a> - <span - className="o-teaser__tag-suffix" - > - 02:51min - </span> - </div> - </div> - <div - className="o-teaser__video" - > - <div - className="o--if-js" - > - <div - className="o-teaser__image-container js-image-container" - > - <div - className="o-video" - data-o-component="o-video" - data-o-video-autorender="true" - data-o-video-data="{\\"renditions\\":[{\\"url\\":\\"https://next-media-api.ft.com/renditions/15218247321960/640x360.mp4\\",\\"width\\":640,\\"height\\":360,\\"mediaType\\":\\"video/mp4\\",\\"codec\\":\\"h264\\"}],\\"mainImageUrl\\":\\"http://com.ft.imagepublish.upp-prod-eu.s3.amazonaws.com/a27ce49b-85b8-445b-b883-db6e2f533194\\"}" - data-o-video-id="0e89d872-5711-457b-80b1-4ca0d8afea46" - data-o-video-placeholder="true" - data-o-video-placeholder-hint="Play video" - data-o-video-placeholder-info="[]" - data-o-video-playsinline="true" - /> - </div> - </div> - <div - className="o--if-no-js" - > - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-eu.s3.amazonaws.com%2Fa27ce49b-85b8-445b-b883-db6e2f533194?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - FT View: Donald Trump, man of steel - - </a> - </div> - <p - className="o-teaser__standfirst" - > - The FT's Rob Armstrong looks at why Donald Trump is pushing trade tariffs - </p> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Article x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--has-image o-teaser--highlight js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Sexual misconduct allegations - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Inside charity fundraiser where hostesses are put on show - - </a> - </div> - <p - className="o-teaser__standfirst" - > - FT investigation finds groping and sexual harassment at secretive black-tie dinner - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Content Package x-teaser 1`] = ` -<div - className="o-teaser o-teaser--package o-teaser--top-story o-teaser--centre o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - FT Magazine - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - The royal wedding - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Prince Harry and Meghan Markle will tie the knot at Windsor Castle - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Opinion Piece x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--has-headshot o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Gideon Rachman - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Anti-Semitism and the threat of identity politics - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Today, hatred of Jews is mixed in with fights about Islam and Israel - </p> - <img - alt="" - aria-hidden="true" - className="o-teaser__headshot" - height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&compression=best&width=75&tint=054593,d6d5d3&dpr=2" - width={75} - /> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Package item x-teaser 1`] = ` -<div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-tag" - > - <span - className="o-teaser__tag-prefix" - > - FT Series - </span> - <a - className="o-teaser__tag" - data-trackable="teaser-tag" - href="#" - > - Financial crisis: Are we safer now? - </a> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why so little has changed since the crash - - </a> - </div> - <p - className="o-teaser__standfirst" - > - Martin Wolf on the power of vested interests in today’s rent-extracting economy - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa25832ea-0053-11e8-9650-9c0ad2d7c5b5?source=next&fit=scale-down&compression=best&width=340" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Paid Post x-teaser 1`] = ` -<div - className="o-teaser o-teaser--paid-post o-teaser--top-story o-teaser--has-image js-teaser" - data-id="" -> - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <div - className="o-teaser__meta-promoted" - > - <span - className="o-teaser__promoted-prefix" - > - Paid post - </span> - <span - className="o-teaser__promoted-by" - > - by UBS - </span> - </div> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="#" - > - Why eSports companies are on a winning streak - - </a> - </div> - <p - className="o-teaser__standfirst" - > - ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020 - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2857%", - } - } - > - <a - aria-hidden="true" - data-trackable="image-link" - href="#" - tab-index="-1" - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Ftpc.googlesyndication.com%2Fpagead%2Fimgad%3Fid%3DCICAgKCrm_3yahABGAEyCMx3RoLss603?source=next&fit=scale-down&compression=best&width=240" - /> - </a> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-teaser renders a TopStory Podcast x-teaser 1`] = ` +exports[`@financial-times/x-gift-article renders a default Free article x-gift-article 1`] = ` <div className="GiftArticle_container__nGwU_" > From 21a1cc07a0397bf1a58f9981832d42ddc90de6ed Mon Sep 17 00:00:00 2001 From: Katerina Loschinina <ekaterina.loshchinina@gmail.com> Date: Fri, 15 Nov 2019 15:51:58 +0000 Subject: [PATCH 297/760] Refactor x-follow-button storybook demo - Use regular story configuration - Clean code from the old-styled stories --- .storybook/config.js | 1 + .storybook/register-components.js | 3 +- .../x-follow-button/stories/follow-button.js | 23 ----------- components/x-follow-button/stories/index.js | 14 ------- components/x-follow-button/stories/knobs.js | 21 ---------- .../x-follow-button/storybook/index.jsx | 39 +++++++++++++++++++ 6 files changed, 41 insertions(+), 60 deletions(-) delete mode 100644 components/x-follow-button/stories/follow-button.js delete mode 100644 components/x-follow-button/stories/index.js delete mode 100644 components/x-follow-button/stories/knobs.js create mode 100644 components/x-follow-button/storybook/index.jsx diff --git a/.storybook/config.js b/.storybook/config.js index f9260ac8d..b21eb1dca 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -13,4 +13,5 @@ configure(() => { // Add regular story definitions (i.e. those using storiesOf() directly below) require('../components/x-increment/storybook/index.jsx'); + require('../components/x-follow-button/storybook/index.jsx'); }, module); diff --git a/.storybook/register-components.js b/.storybook/register-components.js index eeba6aee4..071883d9f 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -4,8 +4,7 @@ const components = [ require('../components/x-teaser/storybook'), require('../components/x-styling-demo/stories'), require('../components/x-gift-article/stories'), - require('../components/x-podcast-launchers/stories'), - require('../components/x-follow-button/stories') + require('../components/x-podcast-launchers/stories') ]; module.exports = components; diff --git a/components/x-follow-button/stories/follow-button.js b/components/x-follow-button/stories/follow-button.js deleted file mode 100644 index ded6ff6d1..000000000 --- a/components/x-follow-button/stories/follow-button.js +++ /dev/null @@ -1,23 +0,0 @@ -exports.title = 'Follow Button'; - -const data = { - isFollowed: false, - variant: 'standard', - conceptNameAsButtonText: false, - conceptId: '00000-0000-00000-00000', - conceptName: 'UK politics & policy', - followPlusDigestEmail: true, - csrfToken: 'testTokenValue' -}; - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = data; - -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = Object.keys(data); - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; diff --git a/components/x-follow-button/stories/index.js b/components/x-follow-button/stories/index.js deleted file mode 100644 index 9d5205a7a..000000000 --- a/components/x-follow-button/stories/index.js +++ /dev/null @@ -1,14 +0,0 @@ -const { FollowButton } = require('../'); - -module.exports = { - component: FollowButton, - dependencies: { - 'o-fonts': '^3.0.0', - 'o-typography': '^5.5.0' - }, - stories: [ - require('./follow-button') - ], - knobs: require('./knobs'), - package: require('../package.json') -}; diff --git a/components/x-follow-button/stories/knobs.js b/components/x-follow-button/stories/knobs.js deleted file mode 100644 index 331320f73..000000000 --- a/components/x-follow-button/stories/knobs.js +++ /dev/null @@ -1,21 +0,0 @@ -// To ensure that component stories do not need to depend on Storybook themselves we return a -// function that may be passed the required dependencies. -module.exports = (data, { text, boolean, select }) => { - return { - conceptNameAsButtonText () { - return boolean('conceptNameAsButtonText', data.conceptNameAsButtonText); - }, - isFollowed () { - return boolean('isFollowed', data.isFollowed); - }, - conceptName () { - return text('Topic name', data.conceptName); - }, - followPlusDigestEmail () { - return boolean('followPlusDigestEmail', data.followPlusDigestEmail); - }, - variant () { - return select('variant', [ 'standard', 'inverse', 'opinion', 'monochrome' ], data.variant); - } - }; -}; diff --git a/components/x-follow-button/storybook/index.jsx b/components/x-follow-button/storybook/index.jsx new file mode 100644 index 000000000..419b73185 --- /dev/null +++ b/components/x-follow-button/storybook/index.jsx @@ -0,0 +1,39 @@ +import React from 'react'; + +import { storiesOf } from '@storybook/react'; +import { withKnobs, text, boolean, select } from '@storybook/addon-knobs'; + +import { FollowButton } from '../src/FollowButton'; + +const defaultProps = { + isFollowed: false, + variant: 'standard', + conceptNameAsButtonText: false, + conceptId: '00000-0000-00000-00000', + conceptName: 'UK politics & policy', + followPlusDigestEmail: true, + csrfToken: 'testTokenValue' +}; + +const toggleConceptNameAsButtonText = () => + boolean('conceptNameAsButtonText', defaultProps.conceptNameAsButtonText); +const toggleIsFollowed = () => boolean('isFollowed', defaultProps.isFollowed); +const toggleConceptName = () => text('Topic name', defaultProps.conceptName); +const toggleFollowPlusDigestEmail = () => + boolean('followPlusDigestEmail', defaultProps.followPlusDigestEmail); +const toggleVariant = () => + select('variant', ['standard', 'inverse', 'opinion', 'monochrome'], defaultProps.variant); + +storiesOf('x-follow-button', module) + .addDecorator(withKnobs) + .add('Follow Button', () => { + const knobs = { + conceptNameAsButtonText: toggleConceptNameAsButtonText(), + isFollowed: toggleIsFollowed(), + conceptName: toggleConceptName(), + followPlusDigestEmail: toggleFollowPlusDigestEmail(), + variant: toggleVariant() + }; + + return <FollowButton {...defaultProps} {...knobs} />; + }); From 4b5843bef05ea9327a4daddc61cb2a6490503f90 Mon Sep 17 00:00:00 2001 From: Katerina Loschinina <ekaterina.loshchinina@gmail.com> Date: Tue, 19 Nov 2019 16:26:43 +0000 Subject: [PATCH 298/760] Add tests - Test conditional behaviour - Modify jest configuration to accomodate tests in the new setup --- __mocks__/styleMock.js | 1 + .../__tests__/x-follow-button.test.jsx | 174 ++++++++++++++++++ components/x-follow-button/package.json | 1 + .../x-follow-button/src/FollowButton.jsx | 22 +-- jest.config.js | 5 +- 5 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 __mocks__/styleMock.js create mode 100644 components/x-follow-button/__tests__/x-follow-button.test.jsx diff --git a/__mocks__/styleMock.js b/__mocks__/styleMock.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/components/x-follow-button/__tests__/x-follow-button.test.jsx b/components/x-follow-button/__tests__/x-follow-button.test.jsx new file mode 100644 index 000000000..ae149fc84 --- /dev/null +++ b/components/x-follow-button/__tests__/x-follow-button.test.jsx @@ -0,0 +1,174 @@ +const { h } = require('@financial-times/x-engine'); +const { mount } = require('@financial-times/x-test-utils/enzyme'); + +import { FollowButton } from '../src/FollowButton'; + +describe('x-follow-button', () => { + describe('concept name', () => { + it('when conceptNameAsButtonText prop is true, and the topic name is provided, the button is named by this name', () => { + const subject = mount( + <FollowButton conceptNameAsButtonText={true} conceptName={'dummy concept name'} /> + ); + expect(subject.find('button').text()).toEqual('dummy concept name'); + }); + + it('when conceptNameAsButtonText prop is false, the button has a default name', () => { + const subject = mount( + <FollowButton conceptNameAsButtonText={false} conceptName={'dummy concept name'} /> + ); + expect(subject.find('button').text()).toEqual('Add to myFT'); + }); + + it('when conceptNameAsButtonText prop is true, and the topic name is not provided, the button has a default name', () => { + const subject = mount(<FollowButton conceptNameAsButtonText={true} />); + expect(subject.find('button').text()).toEqual('Add to myFT'); + }); + + it('when conceptNameAsButtonText prop is not provided, the button has a default name', () => { + const subject = mount(<FollowButton />); + expect(subject.find('button').text()).toEqual('Add to myFT'); + }); + }); + describe('conceptId prop', () => { + it('assigns conceptId prop to data-concept-id attribute of the button', async () => { + const subject = mount(<FollowButton conceptId={'dummy-id'} />); + expect(subject.find('button').prop('data-concept-id')).toEqual('dummy-id'); + }); + + it('assigns conceptId prop to data-concept-id attribute of the form', async () => { + const subject = mount(<FollowButton conceptId={'dummy-id'} />); + expect(subject.find('form').prop('data-concept-id')).toEqual('dummy-id'); + }); + }); + + describe('form action', () => { + it('assigns follow-plus-digest-email put action if followPlusDigestEmail is true', async () => { + const subject = mount(<FollowButton followPlusDigestEmail={true} conceptId={'dummy-id'} />); + expect(subject.find('form').prop('action')).toEqual( + '/__myft/api/core/follow-plus-digest-email/dummy-id?method=put' + ); + }); + + it('assigns followed/concept delete action if isFollowed is true', async () => { + const subject = mount(<FollowButton isFollowed={true} conceptId={'dummy-id'} />); + expect(subject.find('form').prop('action')).toEqual( + '/__myft/api/core/followed/concept/dummy-id?method=delete' + ); + }); + + it('assigns followed/concept put action if isFollowed and followPlusDigestEmail are not passed', async () => { + const subject = mount(<FollowButton conceptId={'dummy-id'} />); + expect(subject.find('form').prop('action')).toEqual( + '/__myft/api/core/followed/concept/dummy-id?method=put' + ); + }); + }); + + describe('isFollowed', () => { + describe('when true', () => { + it('button text is "Added"', () => { + const subject = mount(<FollowButton isFollowed={true} />); + expect(subject.find('button').text()).toEqual('Added'); + }); + + it('button aria-pressed is "true"', () => { + const subject = mount(<FollowButton isFollowed={true} />); + expect(subject.find('button').prop('aria-pressed')).toEqual('true'); + }); + + it('button title is "Remove ConceptName from myFT"', () => { + const subject = mount(<FollowButton isFollowed={true} conceptName={'ConceptName'} />); + expect(subject.find('button').prop('title')).toEqual('Remove ConceptName from myFT'); + }); + + it('button aria-label is "Remove conceptName from myFT"', () => { + const subject = mount(<FollowButton isFollowed={true} conceptName={'ConceptName'} />); + expect(subject.find('button').prop('aria-label')).toEqual('Remove ConceptName from myFT'); + }); + }); + + describe('when false', () => { + it('button text is "Add to myFT"', () => { + const subject = mount(<FollowButton isFollowed={false} />); + expect(subject.find('button').text()).toEqual('Add to myFT'); + }); + + it('button aria-pressed is "false"', () => { + const subject = mount(<FollowButton isFollowed={false} />); + expect(subject.find('button').prop('aria-pressed')).toEqual('false'); + }); + + it('button title is "Add ConceptName to myFT"', () => { + const subject = mount(<FollowButton isFollowed={false} conceptName={'ConceptName'} />); + expect(subject.find('button').prop('title')).toEqual('Add ConceptName to myFT'); + }); + + it('button aria-label is "Add ConceptName to myFT"', () => { + const subject = mount(<FollowButton isFollowed={false} conceptName={'ConceptName'} />); + expect(subject.find('button').prop('aria-label')).toEqual('Add ConceptName to myFT'); + }); + }); + }); + + describe('followPlusDigestEmail', () => { + describe('when true', () => { + it('form has data-myft-ui-variant property which is true', () => { + const subject = mount(<FollowButton followPlusDigestEmail={true} />); + expect(subject.find('form').prop('data-myft-ui-variant')).toEqual(true); + }); + + it('button has data-trackable-context-messaging property which is add-to-myft-plus-digest-button', () => { + const subject = mount(<FollowButton followPlusDigestEmail={true} />); + expect(subject.find('button').prop('data-trackable-context-messaging')).toEqual( + 'add-to-myft-plus-digest-button' + ); + }); + }); + + describe('when false', () => { + it('form has data-myft-ui-variant property which is true', () => { + const subject = mount(<FollowButton followPlusDigestEmail={false} />); + expect(subject.find('form').prop('data-myft-ui-variant')).toEqual(undefined); + }); + + it('button has data-trackable-context-messaging property which is add-to-myft-plus-digest-button', () => { + const subject = mount(<FollowButton followPlusDigestEmail={false} />); + expect(subject.find('button').prop('data-trackable-context-messaging')).toEqual(null); + }); + }); + }); + + describe('form properties', () => { + it('method = GET', () => { + const subject = mount(<FollowButton />); + expect(subject.find('form').prop('method')).toEqual('GET'); + }); + }); + + describe('button properties', () => { + it('data-trackable="follow"', () => { + const subject = mount(<FollowButton />); + expect(subject.find('button').prop('data-trackable')).toEqual('follow'); + }); + + it('type="submit"', () => { + const subject = mount(<FollowButton />); + expect(subject.find('button').prop('type')).toEqual('submit'); + }); + }); + + describe('csrf token', () => { + it('if passed creates an invisible input field', () => { + const subject = mount(<FollowButton csrfToken={'dummyToken'} />); + expect(subject.find('input').prop('value')).toEqual('dummyToken'); + expect(subject.find('input').prop('type')).toEqual('hidden'); + expect(subject.find('input').prop('name')).toEqual('token'); + expect(subject.find('input').prop('data-myft-csrf-token')).toEqual(true); + }); + + it('if not passed an invisible input field is not created', () => { + const subject = mount(<FollowButton csrf={'dummyToken'} />); + expect(subject.find('input')).toEqual({}); + }); + }); +}); diff --git a/components/x-follow-button/package.json b/components/x-follow-button/package.json index fdb286668..a873b6f42 100644 --- a/components/x-follow-button/package.json +++ b/components/x-follow-button/package.json @@ -16,6 +16,7 @@ "license": "ISC", "devDependencies": { "@financial-times/x-rollup": "file:../../packages/x-rollup", + "@financial-times/x-test-utils": "file:../../packages/x-test-utils", "rollup": "^0.57.1", "bower": "^1.7.9", "node-sass": "^4.9.2" diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index dab4dc9f3..807e28a72 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -17,30 +17,31 @@ export const FollowButton = (props) => { const getFormAction = () => { if (followPlusDigestEmail) { - return `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put` + return `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put`; } else if (isFollowed) { - return `/__myft/api/core/followed/concept/${conceptId}?method=delete` + return `/__myft/api/core/followed/concept/${conceptId}?method=delete`; } else { - return `/__myft/api/core/followed/concept/${conceptId}?method=put` + return `/__myft/api/core/followed/concept/${conceptId}?method=put`; } }; const getButtonText = () => { - if (conceptNameAsButtonText) { + if (conceptNameAsButtonText && conceptName) { return conceptName; } - return isFollowed ? `Added` : `Add to myFT`; + return isFollowed ? 'Added' : 'Add to myFT'; }; - const getAccessibleText = () => isFollowed ? `Remove ${conceptName} from myFT` : `Add ${conceptName} to myFT`; + const getAccessibleText = () => + isFollowed ? `Remove ${conceptName} from myFT` : `Add ${conceptName} to myFT`; return ( <form method="GET" data-concept-id={conceptId} action={getFormAction()} - onSubmit={event => { + onSubmit={(event) => { event.preventDefault(); const detail = { action: isFollowed ? 'remove' : 'add', @@ -59,10 +60,7 @@ export const FollowButton = (props) => { event.target.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })); }} {...(followPlusDigestEmail ? { 'data-myft-ui-variant': true } : null)}> - {csrfToken && <input value={csrfToken} - type='hidden' - name='token' - data-myft-csrf-token/>} + {csrfToken && <input value={csrfToken} type="hidden" name="token" data-myft-csrf-token />} <button title={getAccessibleText()} aria-label={getAccessibleText()} @@ -76,7 +74,7 @@ export const FollowButton = (props) => { } data-trackable="follow" type="submit" - dangerouslySetInnerHTML={{__html: getButtonText()}} + dangerouslySetInnerHTML={{ __html: getButtonText() }} /> </form> ); diff --git a/jest.config.js b/jest.config.js index 1008f00bc..52ac7f513 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,9 @@ module.exports = { testMatch: ['**/__tests__/**/*.test.js?(x)'], testPathIgnorePatterns: ['/node_modules/', '/bower_components/'], transform: { - '^.+\\.jsx?$': './packages/x-babel-config/jest', + '^.+\\.jsx?$': './packages/x-babel-config/jest' }, + moduleNameMapper: { + '^[./a-zA-Z0-9$_-]+\\.scss$': '<rootDir>/__mocks__/styleMock.js' + } }; From 5d0c810bc3745d89d24f1aa27702df80e7fb92e2 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 21 Nov 2019 11:06:28 +0000 Subject: [PATCH 299/760] Remove indirect dependency on o-buttons and o-forms Podcast-Launchers uses a mix of methods to include origami css. There is the explicit method (defined in bower) and then an implicit method (by saying in the README that these components are expected) The implicit method here is likely to lead to surprise conflicts because there is no programatic way to check for them. The other downside to this approach is we need _all the compiled css_ just to show a tiny number of styles. This commit explicitly requests oButtons and oForms via bower and then uses the styles via mixins. This is still somewhat messy for oForms as it only provides a high-level mixin which then adds a load of nested classes which you still have to get via the styles object because of CSS modules modifying the class names. --- components/x-podcast-launchers/bower.json | 4 +- .../src/PodcastLaunchers.jsx | 47 +++++++------------ .../src/PodcastLaunchers.scss | 10 +++- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/components/x-podcast-launchers/bower.json b/components/x-podcast-launchers/bower.json index b7c3e50cb..93e87dcb9 100644 --- a/components/x-podcast-launchers/bower.json +++ b/components/x-podcast-launchers/bower.json @@ -4,6 +4,8 @@ "main": "dist/PodcastLaunchers.es5.js", "private": true, "dependencies": { - "o-typography": "^5.12.0" + "o-typography": "^5.12.0", + "o-forms": "^7.0.0", + "o-buttons": "^5.0.0" } } diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.jsx b/components/x-podcast-launchers/src/PodcastLaunchers.jsx index 96906e1b5..7d3da7c2f 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.jsx +++ b/components/x-podcast-launchers/src/PodcastLaunchers.jsx @@ -6,22 +6,10 @@ import acastSeriesIds from './config/series-ids'; import styles from './PodcastLaunchers.scss'; import copyToClipboard from './copy-to-clipboard'; -const basicButtonStyles = [ - 'o-buttons', - 'o-buttons--primary', - 'o-buttons--big' -].join(' '); - -const podcastAppLinkStyles = [ - basicButtonStyles, - styles.podcastAppLink -].join(' '); - -const rssUrlWrapperStyles = [ - 'o-forms-input', - 'o-forms-input--suffix', - 'o-forms-input--text', - styles.rssUrlWrapper +const rssUrlWrapperInner = [ + styles["o-forms-input--suffix"], + styles["o-forms-input--text"], + styles["o-forms-input"] ].join(' '); const noAppWrapperStyles = [ @@ -30,6 +18,7 @@ const noAppWrapperStyles = [ ].join(' '); function defaultFollowButtonRender (conceptId, conceptName, csrfToken, isFollowed) { + console.log(styles) return ( <FollowButton conceptId={conceptId} @@ -71,25 +60,25 @@ class PodcastLaunchers extends Component { <li key={name}> <a href={url} - className={podcastAppLinkStyles} + className={styles.podcastAppLink} data-trackable={trackingId}> {name} </a> </li> ))} - <li key='Rss Url' className={rssUrlWrapperStyles}> - <input className={styles.rssUrlInput} value={rssUrl} type='text' readOnly/> - <div className={styles.rssUrlCopyButton}> - <button - className={basicButtonStyles} - onClick={copyToClipboard} - data-url={rssUrl} - data-trackable='copy-rss' - type='button'> - Copy RSS - </button> - </div> + <li key='Rss Url' className={styles.rssUrlWrapper}> + <span className={rssUrlWrapperInner}> + <input value={rssUrl} type='text' readOnly/> + <button + className={styles.rssUrlCopyButton} + onClick={copyToClipboard} + data-url={rssUrl} + data-trackable='copy-rss' + type='button'> + Copy RSS + </button> + </span> </li> </ul> diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.scss b/components/x-podcast-launchers/src/PodcastLaunchers.scss index 2afc1ae2a..a961137df 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.scss +++ b/components/x-podcast-launchers/src/PodcastLaunchers.scss @@ -1,6 +1,12 @@ $o-typography-is-silent: true; @import 'o-typography/main'; +$o-forms-is-silent: true; +@import 'o-forms/main'; + +$o-buttons-is-silent: true; +@import 'o-buttons/main'; + :global { @import "~@financial-times/x-follow-button/dist/FollowButton"; } @@ -48,11 +54,13 @@ $o-typography-is-silent: true; } .podcastAppLink { + @include oButtons(big, primary); width: 100%; margin-top: 8px; } .rssUrlWrapper { + @include oForms($opts: (elements:(text), features:(suffix))); margin-top: 8px; } @@ -61,7 +69,7 @@ $o-typography-is-silent: true; } .rssUrlCopyButton { - padding-left: 0px; + @include oButtons(big, primary); } .rssUrlCopySpan { From f493ef3e8d69c9ce82998ef0184fd9ef3c51bfec Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 21 Nov 2019 14:38:59 +0000 Subject: [PATCH 300/760] Remove console.log --- components/x-podcast-launchers/src/PodcastLaunchers.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.jsx b/components/x-podcast-launchers/src/PodcastLaunchers.jsx index 7d3da7c2f..21a70c097 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.jsx +++ b/components/x-podcast-launchers/src/PodcastLaunchers.jsx @@ -18,7 +18,6 @@ const noAppWrapperStyles = [ ].join(' '); function defaultFollowButtonRender (conceptId, conceptName, csrfToken, isFollowed) { - console.log(styles) return ( <FollowButton conceptId={conceptId} From 40928510aee94cbcda4cd71da9fe28a1b9656c2d Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 21 Nov 2019 14:40:33 +0000 Subject: [PATCH 301/760] Update README --- components/x-podcast-launchers/readme.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/components/x-podcast-launchers/readme.md b/components/x-podcast-launchers/readme.md index 29ed07b87..d3518706a 100644 --- a/components/x-podcast-launchers/readme.md +++ b/components/x-podcast-launchers/readme.md @@ -23,17 +23,7 @@ The [`x-engine`][engine] module is used to inject your chosen runtime into the c ## Styling -To get correct styling, Your app should have origami components below. -[o-typography](https://registry.origami.ft.com/components/o-typography) -[o-buttons](https://registry.origami.ft.com/components/o-buttons) v5 -:memo: Only needs its `primary` theme classes -``` -$o-buttons-themes: ( - primary: 'primary', -); -``` -[o-forms](https://registry.origami.ft.com/components/o-forms) v7 -:memo: Only uses the text input with suffix button style classes +The styles required for this components are bundled with it. ## Usage @@ -53,7 +43,6 @@ const c = React.createElement(PodcastLaunchers, props); // within your app's sass file @import "@financial-times/x-podcast-launchers/dist/PodcastLaunchers"; ``` -:warning: This component depends on styles provided by o-forms and o-buttons, and therefore o-forms and o-buttons needs to be imported before x-podcast-launchers. All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. From 79caf7c1a847aac7640d2cc9b2a20e35e6b10795 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 21 Nov 2019 15:21:56 +0000 Subject: [PATCH 302/760] Override the o-forms padding --- components/x-podcast-launchers/src/PodcastLaunchers.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.scss b/components/x-podcast-launchers/src/PodcastLaunchers.scss index a961137df..2ef4fa1c6 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.scss +++ b/components/x-podcast-launchers/src/PodcastLaunchers.scss @@ -61,7 +61,9 @@ $o-buttons-is-silent: true; .rssUrlWrapper { @include oForms($opts: (elements:(text), features:(suffix))); - margin-top: 8px; + .o-forms-input { + margin-top: 8px; + } } .rssUrlInput { From d38aa6e63ac1c6721eee0acd719e9ea7ff37b303 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 21 Nov 2019 15:38:16 +0000 Subject: [PATCH 303/760] Update snapshots --- .../__snapshots__/snapshots.test.js.snap | 31 +++++++++---------- .../PodcastLaunchers.test.jsx.snap | 27 ++++++---------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 5e0cd32f6..46f8b13aa 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -569,7 +569,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas > <li> <a - className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + className="PodcastLaunchers_podcastAppLink__69RMR" data-trackable="apple-podcasts" href="podcast://access.acast.com/rss/therachmanreview/abc-123" > @@ -578,7 +578,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas </li> <li> <a - className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + className="PodcastLaunchers_podcastAppLink__69RMR" data-trackable="overcast" href="overcast://x-callback-url/add?url=https://access.acast.com/rss/therachmanreview/abc-123" > @@ -587,7 +587,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas </li> <li> <a - className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + className="PodcastLaunchers_podcastAppLink__69RMR" data-trackable="pocket-casts" href="pktc://subscribe/access.acast.com/rss/therachmanreview/abc-123" > @@ -596,7 +596,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas </li> <li> <a - className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + className="PodcastLaunchers_podcastAppLink__69RMR" data-trackable="podcast-addict" href="podcastaddict://access.acast.com/rss/therachmanreview/abc-123" > @@ -605,7 +605,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas </li> <li> <a - className="o-buttons o-buttons--primary o-buttons--big PodcastLaunchers_podcastAppLink__69RMR" + className="PodcastLaunchers_podcastAppLink__69RMR" data-trackable="acast" href="acast://subscribe/https://access.acast.com/rss/therachmanreview/abc-123" > @@ -613,19 +613,18 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas </a> </li> <li - className="o-forms__affix-wrapper PodcastLaunchers_rssUrlWrapper__vc1zt" + className="PodcastLaunchers_rssUrlWrapper__vc1zt" > - <input - className="o-forms__text PodcastLaunchers_rssUrlInput__O7wL-" - readOnly={true} - type="text" - value="https://access.acast.com/rss/therachmanreview/abc-123" - /> - <div - className="o-forms__suffix PodcastLaunchers_rssUrlCopyButton__2_4c0" + <span + className="PodcastLaunchers_o-forms-input--suffix__rDClE PodcastLaunchers_o-forms-input--text__2jeJ0 PodcastLaunchers_o-forms-input__dI9dQ" > + <input + readOnly={true} + type="text" + value="https://access.acast.com/rss/therachmanreview/abc-123" + /> <button - className="o-buttons o-buttons--primary o-buttons--big" + className="PodcastLaunchers_rssUrlCopyButton__2_4c0" data-trackable="copy-rss" data-url="https://access.acast.com/rss/therachmanreview/abc-123" onClick={[Function]} @@ -633,7 +632,7 @@ exports[`@financial-times/x-podcast-launchers renders a default Example x-podcas > Copy RSS </button> - </div> + </span> </li> </ul> <div diff --git a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap index aec9bc755..bf594e369 100644 --- a/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap +++ b/components/x-podcast-launchers/src/__tests__/__snapshots__/PodcastLaunchers.test.jsx.snap @@ -12,7 +12,6 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = <ul> <li> <a - className="o-buttons o-buttons--primary o-buttons--big " data-trackable="apple-podcasts" href="podcast://acast.access/rss/therachmanreview/123-abc" > @@ -21,7 +20,6 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = </li> <li> <a - className="o-buttons o-buttons--primary o-buttons--big " data-trackable="overcast" href="overcast://x-callback-url/add?url=https://acast.access/rss/therachmanreview/123-abc" > @@ -30,7 +28,6 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = </li> <li> <a - className="o-buttons o-buttons--primary o-buttons--big " data-trackable="pocket-casts" href="pktc://subscribe/acast.access/rss/therachmanreview/123-abc" > @@ -39,7 +36,6 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = </li> <li> <a - className="o-buttons o-buttons--primary o-buttons--big " data-trackable="podcast-addict" href="podcastaddict://acast.access/rss/therachmanreview/123-abc" > @@ -48,27 +44,22 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = </li> <li> <a - className="o-buttons o-buttons--primary o-buttons--big " data-trackable="acast" href="acast://subscribe/https://acast.access/rss/therachmanreview/123-abc" > Acast </a> </li> - <li - className="o-forms__affix-wrapper " - > - <input - className="o-forms__text " - readOnly={true} - type="text" - value="https://acast.access/rss/therachmanreview/123-abc" - /> - <div - className="o-forms__suffix " + <li> + <span + className=" " > + <input + readOnly={true} + type="text" + value="https://acast.access/rss/therachmanreview/123-abc" + /> <button - className="o-buttons o-buttons--primary o-buttons--big" data-trackable="copy-rss" data-url="https://acast.access/rss/therachmanreview/123-abc" onClick={[Function]} @@ -76,7 +67,7 @@ exports[`PodcastLaunchers should render the app links based on concept Id 1`] = > Copy RSS </button> - </div> + </span> </li> </ul> <div From 021c89459446379523966f54af1c24fd600786c5 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Tue, 29 Oct 2019 11:50:12 +0000 Subject: [PATCH 304/760] Fix casing of documented lazy load option for x-teaser component --- components/x-teaser/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index 96bbb3e0d..f4e28e053 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -170,7 +170,7 @@ Property | Type | Notes ----------------|-----------------------|-------------------------------- `image` | [media](#media-props) | `imageSize` | String | XS, Small, Medium, Large, XL or XXL -`imageLazyload` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. +`imageLazyLoad` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. [nimg]: https://github.com/Financial-Times/n-image/ From d67e78d6d001d5b8fbd625b79a0a65e615eddb8e Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Tue, 29 Oct 2019 11:50:49 +0000 Subject: [PATCH 305/760] Fixing casing for lazy loading option in x-teaser type definition --- components/x-teaser/Props.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/Props.d.ts b/components/x-teaser/Props.d.ts index 12851df89..3b12a7852 100644 --- a/components/x-teaser/Props.d.ts +++ b/components/x-teaser/Props.d.ts @@ -69,7 +69,7 @@ export interface Image { /** Images must be accessible to the Origami Image Service */ image?: Media; imageSize?: ImageSize; - imageLazyload?: Boolean | String; + imageLazyLoad?: Boolean | String; } export interface Headshot { From 6ac8b09dbf04fa24112b334e96648cb5d3052343 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe <i-like-robots@users.noreply.github.com> Date: Tue, 29 Oct 2019 11:51:40 +0000 Subject: [PATCH 306/760] Fix casing for lazy load option in x-teaser migration guide --- docs/guides/migrating-to-x-teaser.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/migrating-to-x-teaser.md b/docs/guides/migrating-to-x-teaser.md index db80e2ebf..c92d62c22 100644 --- a/docs/guides/migrating-to-x-teaser.md +++ b/docs/guides/migrating-to-x-teaser.md @@ -139,7 +139,7 @@ Teasers may be configured by providing attributes. Common use cases are provided ### 6. Image lazy loading (optional) -If you have implemented image lazy loading on your pages using [n-image] or [o-lazy-load] you can continue to use this functionality with x-teaser. Setting the `imageLazyload` property to `true` will instruct the component to render the image with a `data-src` property instead of a `src` property. If you need to set a specific class name to identify these images you can set the `imageLazyload` property to a string, which will be appended to list of image class names. +If you have implemented image lazy loading on your pages using [n-image] or [o-lazy-load] you can continue to use this functionality with x-teaser. Setting the `imageLazyload` property to `true` will instruct the component to render the image with a `data-src` property instead of a `src` property. If you need to set a specific class name to identify these images you can set the `imageLazyLoad` property to a string, which will be appended to list of image class names. ```handlebars <!-- if using o-lazy-load --> From 76c96ed167cee27c3ba4c43389a5fc9f164fb209 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 10:36:01 +0000 Subject: [PATCH 307/760] Add Prop for showing on app --- components/x-podcast-launchers/readme.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/x-podcast-launchers/readme.md b/components/x-podcast-launchers/readme.md index d3518706a..146e2a6a3 100644 --- a/components/x-podcast-launchers/readme.md +++ b/components/x-podcast-launchers/readme.md @@ -50,10 +50,12 @@ All `x-` components are designed to be compatible with a variety of runtimes, no ### Properties -Feature | Type | Required | Notes ----------------------|----------|----------|------------------ -`acastRSSHost` | String | Yes | e.g. 'https://acast.access.com' -`conceptId` | String | Yes | -`renderFollowButton` | Function | No | Optional render prop for the follow button +Feature | Type | Required | Notes +----------------------|----------|----------|------------------ +`acastRSSHost` | String | Yes | e.g. 'https://acast.access.com' +`conceptId` | String | Yes | +`renderFollowButton` | Function | No | Optional render prop for the follow button +`alwaysShowAppButtons`| Boolean | No | Defaults to true. In the App we want to always show the podcast apps, + | | | however on desktop we only want to show them at narrow screen widths. Additional props such as the `conceptName` may be required by x-follow-button. Documentation for these is available over in the component's readme. From ed726e4dc4ac877d225a871014c734120ae011a7 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 10:57:38 +0000 Subject: [PATCH 308/760] Bump origami components --- components/x-podcast-launchers/bower.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-podcast-launchers/bower.json b/components/x-podcast-launchers/bower.json index 93e87dcb9..82828bcd4 100644 --- a/components/x-podcast-launchers/bower.json +++ b/components/x-podcast-launchers/bower.json @@ -4,8 +4,8 @@ "main": "dist/PodcastLaunchers.es5.js", "private": true, "dependencies": { - "o-typography": "^5.12.0", - "o-forms": "^7.0.0", - "o-buttons": "^5.0.0" + "o-typography": "^6.0.1", + "o-forms": "^8.0.0", + "o-buttons": "^6.0.2" } } From b907a8ce7b6db65c4f9c58279c4818ffe5f0a8f2 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 10:57:54 +0000 Subject: [PATCH 309/760] Use new oTypography mixins --- components/x-podcast-launchers/src/PodcastLaunchers.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.scss b/components/x-podcast-launchers/src/PodcastLaunchers.scss index 2ef4fa1c6..89791b032 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.scss +++ b/components/x-podcast-launchers/src/PodcastLaunchers.scss @@ -16,18 +16,18 @@ $o-buttons-is-silent: true; } .headingChooseApp { - @include oTypographySansBold($scale: 1); + @include oTypographySans($weight: 'semibold', $scale: 1); margin: 0 0 12px; } .headingNoApp { - @include oTypographySansBold($scale: 0); + @include oTypographySans($weight: 'semibold', $scale: 0); grid-area: heading; margin: 0; } .textNoApp { - @include oTypographySans($scale: 0); + @include oTypographySans(); grid-area: text; margin: 0; } From 0061e81140f7d661aaa5fd0d9d01f5a4b9501644 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 11:22:27 +0000 Subject: [PATCH 310/760] Migrate oButtons mixins --- .../x-podcast-launchers/src/PodcastLaunchers.scss | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.scss b/components/x-podcast-launchers/src/PodcastLaunchers.scss index 89791b032..93e57a61b 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.scss +++ b/components/x-podcast-launchers/src/PodcastLaunchers.scss @@ -54,7 +54,10 @@ $o-buttons-is-silent: true; } .podcastAppLink { - @include oButtons(big, primary); + @include oButtonsContent($opts: ( + 'type': 'primary', + 'size': 'big', + )); width: 100%; margin-top: 8px; } @@ -71,8 +74,10 @@ $o-buttons-is-silent: true; } .rssUrlCopyButton { - @include oButtons(big, primary); -} + @include oButtonsContent($opts: ( + 'type': 'primary', + 'size': 'big', + ));} .rssUrlCopySpan { user-select: text; From e4ac62d20456b88d5b7a01813dc155034f03d2a3 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 11:43:10 +0000 Subject: [PATCH 311/760] Remove README entry committed by accident --- components/x-podcast-launchers/readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/x-podcast-launchers/readme.md b/components/x-podcast-launchers/readme.md index 146e2a6a3..26924cb41 100644 --- a/components/x-podcast-launchers/readme.md +++ b/components/x-podcast-launchers/readme.md @@ -55,7 +55,5 @@ Feature | Type | Required | Notes `acastRSSHost` | String | Yes | e.g. 'https://acast.access.com' `conceptId` | String | Yes | `renderFollowButton` | Function | No | Optional render prop for the follow button -`alwaysShowAppButtons`| Boolean | No | Defaults to true. In the App we want to always show the podcast apps, - | | | however on desktop we only want to show them at narrow screen widths. Additional props such as the `conceptName` may be required by x-follow-button. Documentation for these is available over in the component's readme. From 1d937b07f5a57da3cda584db38b0caa9f05278a8 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 12:00:45 +0000 Subject: [PATCH 312/760] Fix scale arg --- components/x-podcast-launchers/src/PodcastLaunchers.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.scss b/components/x-podcast-launchers/src/PodcastLaunchers.scss index 93e57a61b..adce29483 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.scss +++ b/components/x-podcast-launchers/src/PodcastLaunchers.scss @@ -27,7 +27,7 @@ $o-buttons-is-silent: true; } .textNoApp { - @include oTypographySans(); + @include oTypographySans($scale: 0); grid-area: text; margin: 0; } From 7b146743736b0e518860aeee85bc22eb6e2f8ece Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 13:44:58 +0000 Subject: [PATCH 313/760] Delete commented out code --- .../x-gift-article/src/MobileShareButtons.scss | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/components/x-gift-article/src/MobileShareButtons.scss b/components/x-gift-article/src/MobileShareButtons.scss index 4f41f7d04..86312dab3 100644 --- a/components/x-gift-article/src/MobileShareButtons.scss +++ b/components/x-gift-article/src/MobileShareButtons.scss @@ -1,13 +1,3 @@ -// @mixin socialColor($background-color) { -// @if $background-color == null { -// background-color: transparent; -// } @else if map-has-key($o-share-colors, $background-color) { -// background-color: #{map-get($o-share-colors, $background-color)}; -// } @else { -// background-color: oColorsGetPaletteColor($background-color); -// } -// } - @mixin shareButton($social-media-name, $background-color) { &, &:hover, @@ -32,7 +22,7 @@ } background-color: $background-color; - // @include socialColor($social-media-name); + } } From 307a5564ff9a166891503b08019ef7d614c1930e Mon Sep 17 00:00:00 2001 From: rowanbeentje <rowan@beent.je> Date: Mon, 25 Nov 2019 14:37:42 +0000 Subject: [PATCH 314/760] Modify x-teaser image to support blob: URLs for images without trying to pass them to the image service --- components/x-teaser/readme.md | 2 +- components/x-teaser/src/Image.jsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index f4e28e053..92357a4a5 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -236,7 +236,7 @@ Property | Type | Notes Property | Type | Notes ---------|--------|-------------- -`url` | String | Content UUID or, in the case of images, `data:` URL +`url` | String | Content UUID or, in the case of images, `data:` or `blob:` URL `width` | Number | `height` | Number | diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 4a0dddeac..5c81b4ced 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -28,7 +28,8 @@ const LazyImage = ({ src, lazyLoad }) => { export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { const displayUrl = relativeUrl || url; - const imageSrc = image.url.startsWith('data:') ? image.url : imageService(image.url, ImageSizes[imageSize]); + const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')); + const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize]) : image.url; const ImageComponent = imageLazyLoad ? LazyImage : NormalImage; return image ? ( From c3a7a9ed629b926c42fdc98189857dbbc706a92b Mon Sep 17 00:00:00 2001 From: rowanbeentje <rowan@beent.je> Date: Mon, 25 Nov 2019 14:54:44 +0000 Subject: [PATCH 315/760] Add a x-teaser snashot test covering data: urls --- .../__fixtures__/article-with-data-image.json | 30 ++ .../__snapshots__/snapshots.test.js.snap | 500 ++++++++++++++++++ .../x-teaser/__tests__/snapshots.test.js | 1 + 3 files changed, 531 insertions(+) create mode 100644 components/x-teaser/__fixtures__/article-with-data-image.json diff --git a/components/x-teaser/__fixtures__/article-with-data-image.json b/components/x-teaser/__fixtures__/article-with-data-image.json new file mode 100644 index 000000000..6124a4c98 --- /dev/null +++ b/components/x-teaser/__fixtures__/article-with-data-image.json @@ -0,0 +1,30 @@ +{ + "type": "article", + "id": "", + "url": "#", + "title": "Inside charity fundraiser where hostesses are put on show", + "altTitle": "Men Only, the charity fundraiser with hostesses on show", + "standfirst": "FT investigation finds groping and sexual harassment at secretive black-tie dinner", + "altStandfirst": "Groping and sexual harassment at black-tie dinner charity event", + "publishedDate": "2018-01-23T15:07:00.000Z", + "firstPublishedDate": "2018-01-23T13:53:00.000Z", + "metaPrefixText": "", + "metaSuffixText": "", + "metaLink": { + "url": "#", + "prefLabel": "Sexual misconduct allegations" + }, + "metaAltLink": { + "url": "#", + "prefLabel": "FT Investigations" + }, + "image": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==", + "width": 2048, + "height": 1152, + "imageLazyLoad": "js-image-lazy-load" + }, + "indicators": { + "isEditorsChoice": true + } +} diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index a89d0139c..54377026e 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -60,6 +60,66 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = ` </div> `; +exports[`x-teaser / snapshots renders a Hero teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" + /> + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--hero o-teaser--has-image js-teaser" @@ -546,6 +606,53 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with article data 1`] </div> `; +exports[`x-teaser / snapshots renders a HeroNarrow teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--hero js-teaser" @@ -954,6 +1061,66 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`] </div> `; +exports[`x-teaser / snapshots renders a HeroOverlay teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" + /> + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" @@ -1428,6 +1595,41 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with article data 1`] = </div> `; +exports[`x-teaser / snapshots renders a HeroVideo teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--hero js-teaser" @@ -1818,6 +2020,78 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = ` </div> `; +exports[`x-teaser / snapshots renders a Large teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" + /> + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--large o-teaser--has-image js-teaser" @@ -2376,6 +2650,41 @@ exports[`x-teaser / snapshots renders a Small teaser with article data 1`] = ` </div> `; +exports[`x-teaser / snapshots renders a Small teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--small js-teaser" @@ -2712,6 +3021,78 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`] </div> `; +exports[`x-teaser / snapshots renders a SmallHeavy teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" + /> + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--small o-teaser--has-image js-teaser" @@ -3282,6 +3663,53 @@ exports[`x-teaser / snapshots renders a TopStory teaser with article data 1`] = </div> `; +exports[`x-teaser / snapshots renders a TopStory teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--top-story js-teaser" @@ -3739,6 +4167,78 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da </div> `; +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article-with-data-image data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> + <div + className="o-teaser__image-container js-teaser-image-container" + > + <div + className="o-teaser__image-placeholder" + style={ + Object { + "paddingBottom": "56.2500%", + } + } + > + <a + aria-hidden="true" + data-trackable="image-link" + href="#" + tabIndex="-1" + > + <img + alt="" + className="o-teaser__image" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" + /> + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" diff --git a/components/x-teaser/__tests__/snapshots.test.js b/components/x-teaser/__tests__/snapshots.test.js index ca85dd9ba..5f95c7400 100644 --- a/components/x-teaser/__tests__/snapshots.test.js +++ b/components/x-teaser/__tests__/snapshots.test.js @@ -4,6 +4,7 @@ const { Teaser, presets } = require('../'); const storyData = { article: require('../__fixtures__/article.json'), + 'article-with-data-image': require('../__fixtures__/article-with-data-image.json'), opinion: require('../__fixtures__/opinion.json'), contentPackage: require('../__fixtures__/content-package.json'), packageItem: require('../__fixtures__/package-item.json'), From 96b34f3c721438b4e4c23af2ccaa714678320f69 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 15:18:29 +0000 Subject: [PATCH 316/760] Reformat whitespace to make more readable --- components/x-gift-article/src/Buttons.jsx | 25 ++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 6fe19e8b6..d50666c83 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -31,7 +31,12 @@ export default ({ if (nativeShare) { return ( <div className={ ButtonsClassName }> - <button className={ ButtonWithGapClassNames } type="button" onClick={ actions.shareByNativeShare }>Share link</button> + <button + className={ ButtonWithGapClassNames } + type="button" + onClick={ actions.shareByNativeShare }> + Share link + </button> </div> ); } @@ -43,19 +48,29 @@ export default ({ className={ ButtonWithGapClassNames } type="button" onClick={ shareType === ShareType.gift ? actions.copyGiftUrl : actions.copyNonGiftUrl } - > + > Copy link </button> } - <a className={ ButtonClassNames } href={ mailtoUrl } target="_blank" rel="noopener noreferrer" onClick={ shareType === ShareType.gift ? actions.emailGiftUrl : actions.emailNonGiftUrl }>Email link</a> + <a className={ ButtonClassNames } + href={ mailtoUrl } + target="_blank" + rel="noopener noreferrer" + onClick={ shareType === ShareType.gift ? actions.emailGiftUrl : actions.emailNonGiftUrl }> + Email link + </a> </div> ); } return ( <div className={ ButtonsClassName }> - <button className={ ButtonClassNames } disabled={ !giftCredits } type="button" onClick={ actions.createGiftUrl }> - Create gift link + <button + className={ ButtonClassNames } + disabled={ !giftCredits } + type="button" + onClick={ actions.createGiftUrl }> + Create gift link </button> </div> ); From 49f7584b8a9fc240b99c2c90c8eaa06ea28b5ca4 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 15:37:45 +0000 Subject: [PATCH 317/760] Make GiftArticle a Scss file --- components/x-gift-article/src/Buttons.jsx | 2 +- components/x-gift-article/src/CopyConfirmation.jsx | 2 +- components/x-gift-article/src/Form.jsx | 2 +- components/x-gift-article/src/Loading.jsx | 2 +- components/x-gift-article/src/Message.jsx | 2 +- components/x-gift-article/src/RadioButtonsSection.jsx | 2 +- components/x-gift-article/src/Title.jsx | 2 +- components/x-gift-article/src/Url.jsx | 2 +- components/x-gift-article/src/UrlSection.jsx | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index d50666c83..f39c181fd 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -1,6 +1,6 @@ import { h } from '@financial-times/x-engine'; import { ShareType } from './lib/constants'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; const ButtonsClassName = styles.buttons; diff --git a/components/x-gift-article/src/CopyConfirmation.jsx b/components/x-gift-article/src/CopyConfirmation.jsx index 0440e89a6..380f23827 100644 --- a/components/x-gift-article/src/CopyConfirmation.jsx +++ b/components/x-gift-article/src/CopyConfirmation.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; const confirmationClassNames = [ 'o-message', diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 5a46e12f2..f132878ef 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -4,7 +4,7 @@ import RadioButtonsSection from './RadioButtonsSection'; import UrlSection from './UrlSection'; import MobileShareButtons from './MobileShareButtons'; import CopyConfirmation from './CopyConfirmation'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; const formClassNames = [ 'o-forms', diff --git a/components/x-gift-article/src/Loading.jsx b/components/x-gift-article/src/Loading.jsx index a7d2b37e0..0022566b1 100644 --- a/components/x-gift-article/src/Loading.jsx +++ b/components/x-gift-article/src/Loading.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; export default () => ( <div className={ styles['loading-spinner__container'] }> diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 831c9d1a4..9664c06c5 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -1,6 +1,6 @@ import { h } from '@financial-times/x-engine'; import { ShareType } from './lib/constants'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; const messageClassName = styles.message; const boldTextClassName = styles.bold; diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 5b4c560ed..7c1aa5db2 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -1,6 +1,6 @@ import { h } from '@financial-times/x-engine'; import { ShareType } from './lib/constants'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; const boldTextClassName = styles.bold; const radioSectionClassNames = [ diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx index 1b21cb583..75cf81c36 100644 --- a/components/x-gift-article/src/Title.jsx +++ b/components/x-gift-article/src/Title.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; const titleClassNames = [ styles.title, diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index a4fc6ec31..1937485aa 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -1,6 +1,6 @@ import { h } from '@financial-times/x-engine'; import { ShareType } from './lib/constants'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; const urlClassNames = [ 'o-forms__text', diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index ee8da1c6f..195b7a295 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -3,7 +3,7 @@ import { ShareType } from './lib/constants'; import Url from './Url'; import Message from './Message'; import Buttons from './Buttons'; -import styles from './GiftArticle.css'; +import styles from './GiftArticle.scss'; const urlSectionClassNames = [ 'js-gift-article__url-section', From f0d67f75b5879612bed30c53cc571c5efbc408f3 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 15:40:28 +0000 Subject: [PATCH 318/760] Take oButtons into bower Right now, this component implicitly depends on oButtons which means there is no way to programatically detect dependency issues, if, say, another component in x-dash needed a different version of o-buttons. This commit takes oButtons as a dependency and calls the relevant css via mixins To get this to work I've had to: * Make GiftArticle scss instead of plain css(!) * Create a new class called "buttonBaseStyle" * Remvove oButtons as a dependency listed in stories/index.js * Create a bower file and add oButtons to it. --- components/x-gift-article/bower.json | 9 +++++++++ components/x-gift-article/src/Buttons.jsx | 6 +----- .../src/{GiftArticle.css => GiftArticle.scss} | 7 +++++++ components/x-gift-article/stories/index.js | 1 - 4 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 components/x-gift-article/bower.json rename components/x-gift-article/src/{GiftArticle.css => GiftArticle.scss} (86%) diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json new file mode 100644 index 000000000..55e9e52b1 --- /dev/null +++ b/components/x-gift-article/bower.json @@ -0,0 +1,9 @@ +{ + "name": "@financial-times/x-gift-article", + "description": "", + "main": "dist/GiftArticle.es5.js", + "private": true, + "dependencies": { + "o-buttons": "^5.0.0", + } +} diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index f39c181fd..e604cda4b 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -4,11 +4,7 @@ import styles from './GiftArticle.scss'; const ButtonsClassName = styles.buttons; -const ButtonClassNames = [ - 'o-buttons', - 'o-buttons--primary', - 'o-buttons--big' -].join(' '); +const ButtonClassNames = styles['buttonBaseStyle']; const ButtonWithGapClassNames = [ ButtonClassNames, diff --git a/components/x-gift-article/src/GiftArticle.css b/components/x-gift-article/src/GiftArticle.scss similarity index 86% rename from components/x-gift-article/src/GiftArticle.css rename to components/x-gift-article/src/GiftArticle.scss index 4335be64f..5aa2dba97 100644 --- a/components/x-gift-article/src/GiftArticle.css +++ b/components/x-gift-article/src/GiftArticle.scss @@ -1,3 +1,6 @@ +$o-buttons-is-silent: true; +@import 'o-buttons/main'; + .container { font-family: MetricWeb,sans-serif; } @@ -44,6 +47,10 @@ margin-top: 12px; } +.buttonBaseStyle { + @include oButtonsContent($opts: ('type': 'primary', 'size':'big')); +} + .buttons { grid-area: buttons; text-align: right; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index f4acbea05..af463bbe0 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -6,7 +6,6 @@ exports.package = require('../package.json'); exports.dependencies = { 'o-fonts': '^3.0.0', - 'o-buttons': '^5.13.1', 'o-forms': '^5.7.3', 'o-loading': '^2.2.2', 'o-share': '^6.2.0', From 7cc5cf10bf208578fbaa5424c2dc2bdfc5904614 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 16:24:57 +0000 Subject: [PATCH 319/760] Update oLoading and call explicitly --- components/x-gift-article/.gitignore | 3 ++- components/x-gift-article/bower.json | 1 + components/x-gift-article/src/GiftArticle.scss | 7 +++++++ components/x-gift-article/src/Loading.jsx | 2 +- components/x-gift-article/stories/index.js | 1 - 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/.gitignore b/components/x-gift-article/.gitignore index 53c37a166..67af58370 100644 --- a/components/x-gift-article/.gitignore +++ b/components/x-gift-article/.gitignore @@ -1 +1,2 @@ -dist \ No newline at end of file +dist +package-lock.json diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json index 55e9e52b1..33c4ca11c 100644 --- a/components/x-gift-article/bower.json +++ b/components/x-gift-article/bower.json @@ -5,5 +5,6 @@ "private": true, "dependencies": { "o-buttons": "^5.0.0", + "o-loading": "^4.2.2" } } diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index 5aa2dba97..3c45a0238 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -1,6 +1,9 @@ $o-buttons-is-silent: true; @import 'o-buttons/main'; +$o-loading-is-silent: true; +@import 'o-loading/main'; + .container { font-family: MetricWeb,sans-serif; } @@ -66,6 +69,10 @@ $o-buttons-is-silent: true; margin-top: 8px; } +.loading-spinner { + @include oLoading($opts: ('themes':('dark'), 'sizes':('large'))); +} + .loading-spinner__container { display: flex; justify-content: center; diff --git a/components/x-gift-article/src/Loading.jsx b/components/x-gift-article/src/Loading.jsx index 0022566b1..572d65d1c 100644 --- a/components/x-gift-article/src/Loading.jsx +++ b/components/x-gift-article/src/Loading.jsx @@ -3,6 +3,6 @@ import styles from './GiftArticle.scss'; export default () => ( <div className={ styles['loading-spinner__container'] }> - <div className="o-loading o-loading--dark o-loading--large"></div> + <div className={ styles['loading-spinner']} ></div> </div> ); diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index af463bbe0..ff51cc28e 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -7,7 +7,6 @@ exports.package = require('../package.json'); exports.dependencies = { 'o-fonts': '^3.0.0', 'o-forms': '^5.7.3', - 'o-loading': '^2.2.2', 'o-share': '^6.2.0', 'o-message': '^2.3.3' }; From 54536633b1c3b99a2dbaf17c737d2f9b1d1672b5 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 16:31:25 +0000 Subject: [PATCH 320/760] Fix tests --- .../__snapshots__/snapshots.test.js.snap | 116 +++++++++--------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 46f8b13aa..5c78a90c1 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -2,26 +2,26 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-article 1`] = ` <div - className="GiftArticle_container__nGwU_" + className="GiftArticle_container__2eZcF" > <form name="gift-form" > <fieldset - className="o-forms GiftArticle_form__lC3qs" + className="o-forms GiftArticle_form__3a4SZ" > <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" > Share this article (free) </div> <div - className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__ISfN-" data-section-id="nonGiftLink" data-trackable="nonGiftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__bIcVi" disabled={false} name="non-gift-link" readOnly={true} @@ -29,21 +29,21 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a value="https://www.ft.com/content/blahblahblah?shareType=nongift" /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_message__3UjVE" > This article is currently <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > free </span> for anyone to read </div> <div - className="GiftArticle_buttons__SB7ql" + className="GiftArticle_buttons__1zOQK" > <a - className="o-buttons o-buttons--primary o-buttons--big" + className="GiftArticle_buttonBaseStyle__2ViHW" href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah%3FshareType%3Dnongift" onClick={[Function]} rel="noopener noreferrer" @@ -60,21 +60,21 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a exports[`@financial-times/x-gift-article renders a default With a bad response from membership APIs x-gift-article 1`] = ` <div - className="GiftArticle_container__nGwU_" + className="GiftArticle_container__2eZcF" > <form name="gift-form" > <fieldset - className="o-forms GiftArticle_form__lC3qs" + className="o-forms GiftArticle_form__3a4SZ" > <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" > Share this article (unable to fetch credits) </div> <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__qCYDb" > <input checked={true} @@ -91,7 +91,7 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f > with <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > anyone </span> @@ -112,19 +112,19 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f > with <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > other FT subscribers </span> </label> </div> <div - className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__ISfN-" data-section-id="giftLink" data-trackable="giftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__bIcVi" disabled={true} name="example-gift-link" readOnly={true} @@ -132,11 +132,11 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f value="https://on.ft.com/gift_link" /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_message__3UjVE" > You have <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > gift article credits @@ -144,10 +144,10 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f left this month </div> <div - className="GiftArticle_buttons__SB7ql" + className="GiftArticle_buttons__1zOQK" > <button - className="o-buttons o-buttons--primary o-buttons--big" + className="GiftArticle_buttonBaseStyle__2ViHW" disabled={true} onClick={[Function]} type="button" @@ -163,21 +163,21 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f exports[`@financial-times/x-gift-article renders a default With gift credits x-gift-article 1`] = ` <div - className="GiftArticle_container__nGwU_" + className="GiftArticle_container__2eZcF" > <form name="gift-form" > <fieldset - className="o-forms GiftArticle_form__lC3qs" + className="o-forms GiftArticle_form__3a4SZ" > <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" > Share this article (with credit) </div> <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__qCYDb" > <input checked={true} @@ -194,7 +194,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g > with <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > anyone </span> @@ -215,19 +215,19 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g > with <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > other FT subscribers </span> </label> </div> <div - className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__ISfN-" data-section-id="giftLink" data-trackable="giftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__bIcVi" disabled={true} name="example-gift-link" readOnly={true} @@ -235,11 +235,11 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g value="https://on.ft.com/gift_link" /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_message__3UjVE" > You have <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > gift article credits @@ -247,10 +247,10 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g left this month </div> <div - className="GiftArticle_buttons__SB7ql" + className="GiftArticle_buttons__1zOQK" > <button - className="o-buttons o-buttons--primary o-buttons--big" + className="GiftArticle_buttonBaseStyle__2ViHW" disabled={true} onClick={[Function]} type="button" @@ -266,7 +266,7 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g data-o-component="o-share" > <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" > Share on Social </div> @@ -350,21 +350,21 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g exports[`@financial-times/x-gift-article renders a default With native share on App x-gift-article 1`] = ` <div - className="GiftArticle_container__nGwU_" + className="GiftArticle_container__2eZcF" > <form name="gift-form" > <fieldset - className="o-forms GiftArticle_form__lC3qs" + className="o-forms GiftArticle_form__3a4SZ" > <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" > Share this article (on App) </div> <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__qCYDb" > <input checked={true} @@ -381,7 +381,7 @@ exports[`@financial-times/x-gift-article renders a default With native share on > with <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > anyone </span> @@ -402,19 +402,19 @@ exports[`@financial-times/x-gift-article renders a default With native share on > with <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > other FT subscribers </span> </label> </div> <div - className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__ISfN-" data-section-id="giftLink" data-trackable="giftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__bIcVi" disabled={true} name="example-gift-link" readOnly={true} @@ -422,11 +422,11 @@ exports[`@financial-times/x-gift-article renders a default With native share on value="https://on.ft.com/gift_link" /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_message__3UjVE" > You have <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > gift article credits @@ -434,10 +434,10 @@ exports[`@financial-times/x-gift-article renders a default With native share on left this month </div> <div - className="GiftArticle_buttons__SB7ql" + className="GiftArticle_buttons__1zOQK" > <button - className="o-buttons o-buttons--primary o-buttons--big" + className="GiftArticle_buttonBaseStyle__2ViHW" disabled={true} onClick={[Function]} type="button" @@ -453,21 +453,21 @@ exports[`@financial-times/x-gift-article renders a default With native share on exports[`@financial-times/x-gift-article renders a default Without gift credits x-gift-article 1`] = ` <div - className="GiftArticle_container__nGwU_" + className="GiftArticle_container__2eZcF" > <form name="gift-form" > <fieldset - className="o-forms GiftArticle_form__lC3qs" + className="o-forms GiftArticle_form__3a4SZ" > <div - className="GiftArticle_title__3wb0x GiftArticle_bold__Ys2Sp" + className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" > Share this article (without credit) </div> <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__h1-xz" + className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__qCYDb" > <input checked={true} @@ -484,7 +484,7 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits > with <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > anyone </span> @@ -505,19 +505,19 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits > with <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > other FT subscribers </span> </label> </div> <div - className="js-gift-article__url-section GiftArticle_url-section__Bsa7N" + className="js-gift-article__url-section GiftArticle_url-section__ISfN-" data-section-id="giftLink" data-trackable="giftLink" > <input - className="o-forms__text GiftArticle_url__17SKH" + className="o-forms__text GiftArticle_url__bIcVi" disabled={true} name="example-gift-link" readOnly={true} @@ -525,11 +525,11 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits value="https://on.ft.com/gift_link" /> <div - className="GiftArticle_message__2zqH2" + className="GiftArticle_message__3UjVE" > You have <span - className="GiftArticle_bold__Ys2Sp" + className="GiftArticle_bold__2aMfD" > gift article credits @@ -537,10 +537,10 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits left this month </div> <div - className="GiftArticle_buttons__SB7ql" + className="GiftArticle_buttons__1zOQK" > <button - className="o-buttons o-buttons--primary o-buttons--big" + className="GiftArticle_buttonBaseStyle__2ViHW" disabled={true} onClick={[Function]} type="button" From 9d85aecae5b94ce00c3f56b30984a8d781dd2e1d Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 16:43:49 +0000 Subject: [PATCH 321/760] Add build step for bower install --- components/x-gift-article/.bowerrc | 8 ++++++++ components/x-gift-article/package.json | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 components/x-gift-article/.bowerrc diff --git a/components/x-gift-article/.bowerrc b/components/x-gift-article/.bowerrc new file mode 100644 index 000000000..39039a4a1 --- /dev/null +++ b/components/x-gift-article/.bowerrc @@ -0,0 +1,8 @@ +{ + "registry": { + "search": [ + "https://origami-bower-registry.ft.com", + "https://registry.bower.io" + ] + } +} diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index 5747a8280..8121dd068 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -7,7 +7,7 @@ "module": "dist/GiftArticle.esm.js", "style": "dist/GiftArticle.css", "scripts": { - "prepare": "npm run build", + "prepare": "bower install && npm run build", "build": "node rollup.js", "start": "node rollup.js --watch" }, @@ -23,6 +23,7 @@ }, "devDependencies": { "@financial-times/x-rollup": "file:../../packages/x-rollup", - "node-sass": "^4.9.2" + "node-sass": "^4.9.2", + "bower": "^1.8.8" } } From ed4e4541e2002e5b06b281df08684f51a4aca831 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Mon, 25 Nov 2019 16:44:22 +0000 Subject: [PATCH 322/760] Update bower dependencies to avoid conflicts --- components/x-gift-article/bower.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json index 33c4ca11c..4b32664e0 100644 --- a/components/x-gift-article/bower.json +++ b/components/x-gift-article/bower.json @@ -4,7 +4,7 @@ "main": "dist/GiftArticle.es5.js", "private": true, "dependencies": { - "o-buttons": "^5.0.0", - "o-loading": "^4.2.2" + "o-buttons": "^6.0.0", + "o-loading": "^4.0.0" } } From 78ab7bbedad99c434fc4b7cef803de6766acfdc8 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 26 Nov 2019 12:20:13 +0000 Subject: [PATCH 323/760] Move oForms dep into bower --- components/x-gift-article/bower.json | 3 ++- components/x-gift-article/stories/index.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json index 4b32664e0..62c874f0d 100644 --- a/components/x-gift-article/bower.json +++ b/components/x-gift-article/bower.json @@ -5,6 +5,7 @@ "private": true, "dependencies": { "o-buttons": "^6.0.0", - "o-loading": "^4.0.0" + "o-loading": "^4.0.0", + "o-forms": "^8.0.0" } } diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index ff51cc28e..c6e3948a2 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -6,7 +6,6 @@ exports.package = require('../package.json'); exports.dependencies = { 'o-fonts': '^3.0.0', - 'o-forms': '^5.7.3', 'o-share': '^6.2.0', 'o-message': '^2.3.3' }; From 30ed697adc2ed364e4bbbf0acbd0f9aca24c1a7a Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 26 Nov 2019 12:21:41 +0000 Subject: [PATCH 324/760] Stop using Fieldset As per https://github.com/Financial-Times/o-forms/blob/2bf0485d8f89e4c598bd5e3043d0ae9b0d42adb8/ACCESSIBILITY.md#multiple-inputs > We chose not to work with a fieldset because they are especially difficult to style consistently and we wanted to provide visual flexibility for our users. > But in doing this, we've lost the <fieldset>'s semantic structure, which ATs rely on to read out correctly. > How to make our mimicked stucture more semantic? Enter aria (and role) attributes! This commit replaces the Fieldset element with a div and adds the aria attributes as suggested by the origami team to replace the semantic meaning of fieldset. --- components/x-gift-article/src/Form.jsx | 10 ++++++---- components/x-gift-article/src/GiftArticle.scss | 2 +- components/x-gift-article/src/Title.jsx | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index f132878ef..0cd22f6c3 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -7,14 +7,16 @@ import CopyConfirmation from './CopyConfirmation'; import styles from './GiftArticle.scss'; const formClassNames = [ - 'o-forms', - styles.form + styles["share-form"] ].join(' '); export default (props) => ( <div className={ styles.container }> <form name="gift-form"> - <fieldset className={ formClassNames }> + <div className={ formClassNames } + role="group" + arialabelledby="gift-article-title"> + <Title title={ props.title }/> { !props.isFreeArticle && <RadioButtonsSection @@ -24,7 +26,7 @@ export default (props) => ( } <UrlSection {...props} /> - </fieldset> + </div> </form> { props.showCopyConfirmation && diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index 3c45a0238..03ca9e79b 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -8,7 +8,7 @@ $o-loading-is-silent: true; font-family: MetricWeb,sans-serif; } -.form { +.share-form { max-width: none; padding: 0; margin: 0; diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx index 75cf81c36..0d600acaa 100644 --- a/components/x-gift-article/src/Title.jsx +++ b/components/x-gift-article/src/Title.jsx @@ -7,5 +7,5 @@ const titleClassNames = [ ].join(' '); export default ({ title }) => ( - <div className={ titleClassNames }>{ title }</div> + <div className={ titleClassNames } id="gift-article-title">{ title }</div> ); From d45f417eccb8f28fe547b2ab0dab2a9192dcc0a8 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 26 Nov 2019 12:24:39 +0000 Subject: [PATCH 325/760] Todos for future typography work --- components/x-gift-article/src/GiftArticle.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index 03ca9e79b..a91e6e0eb 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -5,6 +5,7 @@ $o-loading-is-silent: true; @import 'o-loading/main'; .container { + // todo replace with oTypography font-family: MetricWeb,sans-serif; } @@ -15,6 +16,7 @@ $o-loading-is-silent: true; } .bold { + // TODO remove this class font-weight: 600; } @@ -34,6 +36,7 @@ $o-loading-is-silent: true; } .title { + // TODO import oTypography here font-size: 20px; line-height: 24px; margin-bottom: 20px; From a0fd56175739b5d28760126268c8d041c85d2d26 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 26 Nov 2019 14:29:31 +0000 Subject: [PATCH 326/760] Use oForms for the text input --- components/x-gift-article/src/Form.jsx | 7 +++-- .../x-gift-article/src/GiftArticle.scss | 10 ++++++- components/x-gift-article/src/Url.jsx | 27 ++++++++++++------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 0cd22f6c3..f3e6df08d 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -12,11 +12,10 @@ const formClassNames = [ export default (props) => ( <div className={ styles.container }> - <form name="gift-form"> - <div className={ formClassNames } - role="group" + <form name="gift-form" className={ formClassNames }> + <div role="group" arialabelledby="gift-article-title"> - + <Title title={ props.title }/> { !props.isFreeArticle && <RadioButtonsSection diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index a91e6e0eb..2dd7074a3 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -4,6 +4,9 @@ $o-buttons-is-silent: true; $o-loading-is-silent: true; @import 'o-loading/main'; +$o-forms-is-silent: true; +@import 'o-forms/main'; + .container { // todo replace with oTypography font-family: MetricWeb,sans-serif; @@ -13,6 +16,11 @@ $o-loading-is-silent: true; max-width: none; padding: 0; margin: 0; + + @include oForms($opts: ( + 'elements': ('text', 'radio'), + 'features': ('inline', 'disabled') + )); } .bold { @@ -42,7 +50,7 @@ $o-loading-is-silent: true; margin-bottom: 20px; } -.url { +.url-input { grid-area: share-url; max-width: none; } diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 1937485aa..7854c1b68 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -2,20 +2,27 @@ import { h } from '@financial-times/x-engine'; import { ShareType } from './lib/constants'; import styles from './GiftArticle.scss'; + +const urlWrapperClassNames = [ + styles['o-forms-input'], + styles['o-forms-input--text'] +].join(' '); + const urlClassNames = [ - 'o-forms__text', - styles.url + styles['url-input'] ].join(' '); export default ({ shareType, isGiftUrlCreated, url, urlType }) => { return ( - <input - type="text" - name={ urlType } - value={ url } - className={ urlClassNames } - disabled={ shareType === ShareType.gift && !isGiftUrlCreated } - readOnly - /> + <span className={ urlWrapperClassNames }> + <input + type="text" + name={ urlType } + value={ url } + className={ urlClassNames } + disabled={ shareType === ShareType.gift && !isGiftUrlCreated } + readOnly + /> + </span> ); }; From 217e92b1ffde1b705aaf01632558884594323724 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 26 Nov 2019 14:58:33 +0000 Subject: [PATCH 327/760] Update o-forms radio buttons --- .../x-gift-article/src/GiftArticle.scss | 2 +- .../src/RadioButtonsSection.jsx | 55 ++++++++++--------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index 2dd7074a3..b39be2149 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -18,7 +18,7 @@ $o-forms-is-silent: true; margin: 0; @include oForms($opts: ( - 'elements': ('text', 'radio'), + 'elements': ('text', 'radio-round'), 'features': ('inline', 'disabled') )); } diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 7c1aa5db2..4ab3469a7 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -4,39 +4,42 @@ import styles from './GiftArticle.scss'; const boldTextClassName = styles.bold; const radioSectionClassNames = [ - 'o-forms__group', - 'o-forms__group--inline', + styles['o-forms-input'], + styles['o-forms-input--radio-round'], + styles['o-forms-input--inline'], + styles['o-forms-field'], styles['radio-button-section'] ].join(' '); export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( <div className={ radioSectionClassNames }> - - <input - type="radio" - name="gift-form__radio" - value="giftLink" - className="o-forms__radio" - id="giftLink" - checked={ shareType === ShareType.gift } - onChange={ showGiftUrlSection } - /> - - <label htmlFor="giftLink" className="o-forms__label"> - with <span className={ boldTextClassName }>anyone</span> (uses 1 gift credit) + <label htmlFor="giftLink"> + <input + type="radio" + name="gift-form__radio" + value="giftLink" + className="o-forms__radio" + id="giftLink" + checked={ shareType === ShareType.gift } + onChange={ showGiftUrlSection } + /> + <span className={ styles["o-forms-input__label"] }> + with <span className={ boldTextClassName }>anyone</span> (uses 1 gift credit) + </span> </label> - <input - type="radio" - name="gift-form__radio" - value="nonGiftLink" - className="o-forms__radio" - id="nonGiftLink" - checked={ shareType === ShareType.nonGift } - onChange={ showNonGiftUrlSection }/> - - <label htmlFor="nonGiftLink" className="o-forms__label"> - with <span className={ boldTextClassName }>other FT subscribers</span> + <label htmlFor="nonGiftLink"> + <input + type="radio" + name="gift-form__radio" + value="nonGiftLink" + className="o-forms__radio" + id="nonGiftLink" + checked={ shareType === ShareType.nonGift } + onChange={ showNonGiftUrlSection }/> + <span className={ styles["o-forms-input__label"] }> + with <span className={ boldTextClassName }>other FT subscribers</span> + </span> </label> </div> From 19c1a16acb89861cafd4501a41743d7522dd60b4 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 26 Nov 2019 15:05:10 +0000 Subject: [PATCH 328/760] Make alphabetical --- components/x-gift-article/bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json index 62c874f0d..97f7d2d27 100644 --- a/components/x-gift-article/bower.json +++ b/components/x-gift-article/bower.json @@ -5,7 +5,7 @@ "private": true, "dependencies": { "o-buttons": "^6.0.0", - "o-loading": "^4.0.0", "o-forms": "^8.0.0" + "o-loading": "^4.0.0", } } From 828f328fc00dcce97e10566d7cc056b376a9d4f2 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 26 Nov 2019 15:34:32 +0000 Subject: [PATCH 329/760] Use oMessage styles explicitly Rather than expecting these styles to be on the page, add oMessage to bower and then include the oMessage mixin in the Sass. --- components/x-gift-article/bower.json | 4 +++- .../x-gift-article/src/CopyConfirmation.jsx | 17 +++++++++-------- .../x-gift-article/src/GiftArticle.scss | 19 +++++++++++++++---- components/x-gift-article/stories/index.js | 3 +-- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json index 97f7d2d27..a00bb356a 100644 --- a/components/x-gift-article/bower.json +++ b/components/x-gift-article/bower.json @@ -5,7 +5,9 @@ "private": true, "dependencies": { "o-buttons": "^6.0.0", - "o-forms": "^8.0.0" + "o-forms": "^8.0.0", "o-loading": "^4.0.0", + "o-message": "^4.0.0" + } } diff --git a/components/x-gift-article/src/CopyConfirmation.jsx b/components/x-gift-article/src/CopyConfirmation.jsx index 380f23827..a98b1862a 100644 --- a/components/x-gift-article/src/CopyConfirmation.jsx +++ b/components/x-gift-article/src/CopyConfirmation.jsx @@ -2,23 +2,24 @@ import { h } from '@financial-times/x-engine'; import styles from './GiftArticle.scss'; const confirmationClassNames = [ - 'o-message', - 'o-message--alert-bleed', - 'o-message--success', + styles['o-message'], + styles['o-message--alert'], + + styles['o-message--success'], styles['copy-confirmation'] ].join(' '); export default ({ hideCopyConfirmation }) => ( <div className={ confirmationClassNames }> - <div className="o-message__container"> + <div className={ styles["o-message__container"] }> - <div className="o-message__content"> - <p className="o-message__content-main"> - <span className="o-message__content-highlight">The link has been copied to your clipboard</span> + <div className={ styles["o-message__content"] }> + <p className={ styles["o-message__content-main"]}> + <span className={ styles["o-message__content-highlight"] }>The link has been copied to your clipboard</span> </p> </div> - <button className="o-message__close" aria-label="close" title="Close" onClick={ hideCopyConfirmation }></button> + <button className={ styles["o-message__close"]} aria-label="close" title="Close" onClick={ hideCopyConfirmation }></button> </div> </div> diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index b39be2149..349ae6a9d 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -7,6 +7,11 @@ $o-loading-is-silent: true; $o-forms-is-silent: true; @import 'o-forms/main'; +$system-code: "github:Financial-Times/x-dash"; + +$o-message-is-silent: true; +@import 'o-message/main'; + .container { // todo replace with oTypography font-family: MetricWeb,sans-serif; @@ -55,6 +60,16 @@ $o-forms-is-silent: true; max-width: none; } + +.copy-confirmation { + margin-top: 8px; +} + +@include oMessage($opts: ( + 'types': ('alert'), + 'states': ('success'), +)); + .message { grid-area: message; font-size: 16px; @@ -76,10 +91,6 @@ $o-forms-is-silent: true; margin-right: 5px; } -.copy-confirmation { - margin-top: 8px; -} - .loading-spinner { @include oLoading($opts: ('themes':('dark'), 'sizes':('large'))); } diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index c6e3948a2..1fda5106a 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -6,8 +6,7 @@ exports.package = require('../package.json'); exports.dependencies = { 'o-fonts': '^3.0.0', - 'o-share': '^6.2.0', - 'o-message': '^2.3.3' + 'o-share': '^6.2.0' }; exports.stories = [ From 3ff6991e1d2258ecb4ebe7303236591fc960e411 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Tue, 26 Nov 2019 17:15:49 +0000 Subject: [PATCH 330/760] Remove oShare The oShare component provides styles and a little JavaScipt for sharing. This component is bastardising the share styles quite a lot, and the JavaScript is quite minimal (only used for popping sharing out in it's own window) so I think we can remove it and get the share icons directly from the image service. This will mean the orgiami team are not blocked from changes to oshare by this weird usecase. --- components/x-gift-article/bower.json | 1 - components/x-gift-article/readme.md | 1 - .../x-gift-article/src/GiftArticle.scss | 4 +-- .../x-gift-article/src/MobileShareButtons.jsx | 25 +++++------------ .../src/MobileShareButtons.scss | 28 +++++++++++++++++-- .../x-gift-article/src/lib/variables.scss | 3 ++ components/x-gift-article/stories/index.js | 3 +- 7 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 components/x-gift-article/src/lib/variables.scss diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json index a00bb356a..e65f47e46 100644 --- a/components/x-gift-article/bower.json +++ b/components/x-gift-article/bower.json @@ -8,6 +8,5 @@ "o-forms": "^8.0.0", "o-loading": "^4.0.0", "o-message": "^4.0.0" - } } diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index b40940d10..d20849768 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -17,7 +17,6 @@ To get correct styling, Your app should have origami components below. [o-buttons](https://registry.origami.ft.com/components/o-buttons) [o-forms](https://registry.origami.ft.com/components/o-forms) [o-loading](https://registry.origami.ft.com/components/o-loading) -[o-share](https://registry.origami.ft.com/components/o-share) [o-message](https://registry.origami.ft.com/components/o-message) ## Usage diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index 349ae6a9d..f4531c6ae 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -1,3 +1,5 @@ +@import 'src/lib/variables'; + $o-buttons-is-silent: true; @import 'o-buttons/main'; @@ -7,8 +9,6 @@ $o-loading-is-silent: true; $o-forms-is-silent: true; @import 'o-forms/main'; -$system-code: "github:Financial-Times/x-dash"; - $o-message-is-silent: true; @import 'o-message/main'; diff --git a/components/x-gift-article/src/MobileShareButtons.jsx b/components/x-gift-article/src/MobileShareButtons.jsx index 685ab9869..fc2620e1e 100644 --- a/components/x-gift-article/src/MobileShareButtons.jsx +++ b/components/x-gift-article/src/MobileShareButtons.jsx @@ -3,67 +3,56 @@ import Title from './Title'; import styles from './MobileShareButtons.scss'; const containerClassNames = [ - 'o-share', - 'o-share--inverse', styles.container ].join(' '); const buttonClassNames = [ - 'o-share__action', styles.button ].join(' '); const whatsappButtonClassNames = [ buttonClassNames, - 'o-share__action--whatsapp' + styles.whatsapp ].join(' '); const facebookClassNames = [ - 'o-share__icon', - 'o-share__icon--facebook', styles.facebook ].join(' '); const twitterClassNames = [ - 'o-share__icon', - 'o-share__icon--twitter', styles.twitter ].join(' '); const linkedinClassNames = [ - 'o-share__icon', - 'o-share__icon--linkedin', styles.linkedin ].join(' '); const whatsappClassNames = [ - 'o-share__icon', - 'o-share__icon--whatsapp', styles.whatsapp ].join(' '); export default ({ mobileShareLinks }) => ( - <div className={ containerClassNames } data-o-component="o-share"> + <div className={ containerClassNames }> <Title title={ 'Share on Social' }/> <ul> <li className={ buttonClassNames } data-share="facebook"> <a className={ facebookClassNames } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook"> - Facebook <span className="o-share__text">(opens new window)</span> + Facebook <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> </li> <li className={ buttonClassNames } data-share="twitter"> <a className={ twitterClassNames } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter"> - Twitter <span className="o-share__text">(opens new window)</span> + Twitter <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> </li> <li className={ buttonClassNames } data-share="linkedin"> <a className={ linkedinClassNames } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin"> - LinkedIn <span className="o-share__text">(opens new window)</span> + LinkedIn <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> </li> - <li className={ whatsappButtonClassNames } data-share="whatsapp"> + <li className={ buttonClassNames } data-share="whatsapp"> <a className={ whatsappClassNames } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp"> - Whatsapp <span className="o-share__text">(opens new window)</span> + Whatsapp <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> </li> </ul> diff --git a/components/x-gift-article/src/MobileShareButtons.scss b/components/x-gift-article/src/MobileShareButtons.scss index 86312dab3..62438ac98 100644 --- a/components/x-gift-article/src/MobileShareButtons.scss +++ b/components/x-gift-article/src/MobileShareButtons.scss @@ -1,3 +1,5 @@ +@import 'src/lib/variables'; + @mixin shareButton($social-media-name, $background-color) { &, &:hover, @@ -6,14 +8,17 @@ &:focus { width: 100%; color: #fff; - border: 1px solid #fff !important; display: flex; align-items: center; justify-content: center; text-decoration: none; - font-family: MetricWeb,sans-serif; font-size: 16px; font-weight: 600; + background-image: url(socialIconUrl($social-media-name)); + background-position: left; + background-repeat: no-repeat; + background-size: 40px; + height: 40px; &:before { position: absolute; @@ -26,10 +31,17 @@ } } +@function socialIconUrl($icon-name) { + $image-service-icon-url: "https://www.ft.com/__origami/service/image/v2/images/raw/ftsocial-v2:#{$icon-name}?source=#{$system-code}&tint=white"; + + @return $image-service-icon-url; +} + .container { margin-top: 36px; width: 100%; ul { + decoration: none; padding-left: 0; margin-left: -10px; } @@ -40,6 +52,18 @@ margin: 10px 0 0 10px; } +.hidden-button-text { + position: absolute; + clip: rect(0 0 0 0); + margin: -1px; + border: 0; + overflow: hidden; + padding: 0; + width: 1px; + height: 1px; + white-space: nowrap; +} + .facebook { @include shareButton('facebook', #3b579d); } diff --git a/components/x-gift-article/src/lib/variables.scss b/components/x-gift-article/src/lib/variables.scss new file mode 100644 index 000000000..e76088485 --- /dev/null +++ b/components/x-gift-article/src/lib/variables.scss @@ -0,0 +1,3 @@ +// This is needed for calls to the image service for Icons used in Social, +// and oMessage +$system-code:'github:Financial-Times/x-dash'; diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 1fda5106a..be9479e8a 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -5,8 +5,7 @@ exports.component = GiftArticle; exports.package = require('../package.json'); exports.dependencies = { - 'o-fonts': '^3.0.0', - 'o-share': '^6.2.0' + 'o-fonts': '^3.0.0' }; exports.stories = [ From 89966cea9743c3e86daba604e24f58f7c722d6be Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 10:09:55 +0000 Subject: [PATCH 331/760] Update README --- components/x-gift-article/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index d20849768..6eecad9fb 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -77,7 +77,7 @@ Property | Type | Required | Note --------------------------|---------|----------|---- `isFreeArticle` | Boolean | yes | Only non gift form is displayed when this value is `true`. `article` | Object | yes | Must contain `id`, `title` and `url` properties -`showMobileShareLinks` | Boolean | no | +`showMobileShareLinks` | Boolean | no | For ft.com on mobile sharing. `nativeShare` | Boolean | no | This is a property for App to display Native Sharing. `apiProtocol` | String | no | The protocol to use when making requests to the gift article and URL shortening services. Ignored if `apiDomain` is not set. `apiDomain` | String | no | The domain to use when making requests to the gift article and URL shortening services. From 722c8dd00b5664207eba8110e837c29cf341350b Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 10:20:21 +0000 Subject: [PATCH 332/760] Remove list from social buttons No need for this to be wrapped in a list. Semantically it is fine to not have it, and from an accessibility standpoint removing the list makes these mobile links less noisy for screen reader users. --- .../x-gift-article/src/MobileShareButtons.jsx | 20 +++++++++---------- .../src/MobileShareButtons.scss | 5 ++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/components/x-gift-article/src/MobileShareButtons.jsx b/components/x-gift-article/src/MobileShareButtons.jsx index fc2620e1e..1e0a2b980 100644 --- a/components/x-gift-article/src/MobileShareButtons.jsx +++ b/components/x-gift-article/src/MobileShareButtons.jsx @@ -34,27 +34,27 @@ const whatsappClassNames = [ export default ({ mobileShareLinks }) => ( <div className={ containerClassNames }> <Title title={ 'Share on Social' }/> - <ul> - <li className={ buttonClassNames } data-share="facebook"> + <div className={ styles["container-inner"] }> + <span className={ buttonClassNames } data-share="facebook"> <a className={ facebookClassNames } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook"> Facebook <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> - </li> - <li className={ buttonClassNames } data-share="twitter"> + </span> + <span className={ buttonClassNames } data-share="twitter"> <a className={ twitterClassNames } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter"> Twitter <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> - </li> - <li className={ buttonClassNames } data-share="linkedin"> + </span> + <span className={ buttonClassNames } data-share="linkedin"> <a className={ linkedinClassNames } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin"> LinkedIn <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> - </li> - <li className={ buttonClassNames } data-share="whatsapp"> + </span> + <span className={ buttonClassNames } data-share="whatsapp"> <a className={ whatsappClassNames } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp"> Whatsapp <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> - </li> - </ul> + </span> + </div> </div> ); diff --git a/components/x-gift-article/src/MobileShareButtons.scss b/components/x-gift-article/src/MobileShareButtons.scss index 62438ac98..673f46fb8 100644 --- a/components/x-gift-article/src/MobileShareButtons.scss +++ b/components/x-gift-article/src/MobileShareButtons.scss @@ -40,9 +40,7 @@ .container { margin-top: 36px; width: 100%; - ul { - decoration: none; - padding-left: 0; + .container-inner { margin-left: -10px; } } @@ -50,6 +48,7 @@ .button { width: calc(50% - 10px); margin: 10px 0 0 10px; + display: inline-block; } .hidden-button-text { From 766757b05393947933d765130d66e96c9b6b6152 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 11:28:05 +0000 Subject: [PATCH 333/760] fix margin --- components/x-gift-article/src/GiftArticle.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index f4531c6ae..22e611f2e 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -26,6 +26,10 @@ $o-message-is-silent: true; 'elements': ('text', 'radio-round'), 'features': ('inline', 'disabled') )); + + .radio-button-section { + margin-bottom: 12px; + } } .bold { @@ -33,10 +37,6 @@ $o-message-is-silent: true; font-weight: 600; } -.radio-button-section { - margin-bottom: 12px; -} - @media only screen and (min-width: 600px) { .url-section { display: grid; From f8497b7f67df10d2cbdfefde574eef4534a1d576 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 11:29:09 +0000 Subject: [PATCH 334/760] fix margin --- components/x-gift-article/src/GiftArticle.scss | 5 ----- components/x-gift-article/src/Message.jsx | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index 22e611f2e..c6bfc338f 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -32,11 +32,6 @@ $o-message-is-silent: true; } } -.bold { - // TODO remove this class - font-weight: 600; -} - @media only screen and (min-width: 600px) { .url-section { display: grid; diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 9664c06c5..6e96356da 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -10,7 +10,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month if (isFreeArticle) { return ( <div className={ messageClassName }> - This article is currently <span className={ boldTextClassName }>free</span> for anyone to read + This article is currently <strong>free</strong> for anyone to read </div> ); } From 4f82c77311e1e46f7a627cc70cbddf8c993d3b58 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 11:35:34 +0000 Subject: [PATCH 335/760] Replace <span class="bold">[...] with <strong> We can style the <strong> element to have font weight 600 rather than the browser default (which is too heavy) and use the semantically correct <strong> tag for these elements. --- components/x-gift-article/src/GiftArticle.scss | 5 +++++ components/x-gift-article/src/Message.jsx | 7 +++---- components/x-gift-article/src/RadioButtonsSection.jsx | 5 ++--- components/x-gift-article/src/Title.jsx | 3 +-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index c6bfc338f..ff312f2fd 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -15,8 +15,13 @@ $o-message-is-silent: true; .container { // todo replace with oTypography font-family: MetricWeb,sans-serif; + + strong { + font-weight: 600; + } } + .share-form { max-width: none; padding: 0; diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 6e96356da..bb5d7d9dc 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -3,7 +3,6 @@ import { ShareType } from './lib/constants'; import styles from './GiftArticle.scss'; const messageClassName = styles.message; -const boldTextClassName = styles.bold; export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, nextRenewalDateText, redemptionLimit, invalidResponseFromApi }) => { @@ -19,8 +18,8 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month if (giftCredits === 0) { return ( <div className={ messageClassName }> - You’ve used all your <span className={ boldTextClassName }>gift article credits</span><br /> - You’ll get your next { monthlyAllowance } on <span className={ boldTextClassName }>{ nextRenewalDateText }</span> + You’ve used all your <strong>gift article credits</strong><br /> + You’ll get your next { monthlyAllowance } on <strong>{ nextRenewalDateText }</strong> </div> ); } @@ -43,7 +42,7 @@ export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, month return ( <div className={ messageClassName }> - You have <span className={ boldTextClassName }>{ giftCredits } gift article { giftCredits === 1 ? 'credit' : 'credits' }</span> left this month + You have <strong>{ giftCredits } gift article { giftCredits === 1 ? 'credit' : 'credits' }</strong> left this month </div> ); } diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 4ab3469a7..6d8e563e4 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -2,7 +2,6 @@ import { h } from '@financial-times/x-engine'; import { ShareType } from './lib/constants'; import styles from './GiftArticle.scss'; -const boldTextClassName = styles.bold; const radioSectionClassNames = [ styles['o-forms-input'], styles['o-forms-input--radio-round'], @@ -24,7 +23,7 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( onChange={ showGiftUrlSection } /> <span className={ styles["o-forms-input__label"] }> - with <span className={ boldTextClassName }>anyone</span> (uses 1 gift credit) + with <strong>anyone</strong> (uses 1 gift credit) </span> </label> @@ -38,7 +37,7 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( checked={ shareType === ShareType.nonGift } onChange={ showNonGiftUrlSection }/> <span className={ styles["o-forms-input__label"] }> - with <span className={ boldTextClassName }>other FT subscribers</span> + with <strong>other FT subscribers</strong> </span> </label> diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx index 0d600acaa..592263a77 100644 --- a/components/x-gift-article/src/Title.jsx +++ b/components/x-gift-article/src/Title.jsx @@ -2,8 +2,7 @@ import { h } from '@financial-times/x-engine'; import styles from './GiftArticle.scss'; const titleClassNames = [ - styles.title, - styles.bold + styles.title ].join(' '); export default ({ title }) => ( From c54df6b677188021aaebaa914498efce4d6794b6 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 11:47:35 +0000 Subject: [PATCH 336/760] Use oTypography --- components/x-gift-article/bower.json | 3 ++- components/x-gift-article/src/GiftArticle.scss | 13 ++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json index e65f47e46..64ccecb99 100644 --- a/components/x-gift-article/bower.json +++ b/components/x-gift-article/bower.json @@ -7,6 +7,7 @@ "o-buttons": "^6.0.0", "o-forms": "^8.0.0", "o-loading": "^4.0.0", - "o-message": "^4.0.0" + "o-message": "^4.0.0", + "o-typography": "6.0.0" } } diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index ff312f2fd..fab96f671 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -12,10 +12,11 @@ $o-forms-is-silent: true; $o-message-is-silent: true; @import 'o-message/main'; -.container { - // todo replace with oTypography - font-family: MetricWeb,sans-serif; +$o-typography-is-silent: true; +@import 'o-typography/main'; +.container { + @include oTypographySans; strong { font-weight: 600; } @@ -49,10 +50,8 @@ $o-message-is-silent: true; } .title { - // TODO import oTypography here - font-size: 20px; - line-height: 24px; - margin-bottom: 20px; + @include oTypographyHeading(4); + } .url-input { From 33a89b48161821b08abc0d4d2931f896dbb8b46d Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 11:51:51 +0000 Subject: [PATCH 337/760] Tidy up MobileShareButtons --- .../x-gift-article/src/MobileShareButtons.jsx | 46 ++++--------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/components/x-gift-article/src/MobileShareButtons.jsx b/components/x-gift-article/src/MobileShareButtons.jsx index 1e0a2b980..f6806fc36 100644 --- a/components/x-gift-article/src/MobileShareButtons.jsx +++ b/components/x-gift-article/src/MobileShareButtons.jsx @@ -2,56 +2,28 @@ import { h } from '@financial-times/x-engine'; import Title from './Title'; import styles from './MobileShareButtons.scss'; -const containerClassNames = [ - styles.container -].join(' '); - -const buttonClassNames = [ - styles.button -].join(' '); - -const whatsappButtonClassNames = [ - buttonClassNames, - styles.whatsapp -].join(' '); - -const facebookClassNames = [ - styles.facebook -].join(' '); - -const twitterClassNames = [ - styles.twitter -].join(' '); - -const linkedinClassNames = [ - styles.linkedin -].join(' '); - -const whatsappClassNames = [ - styles.whatsapp -].join(' '); export default ({ mobileShareLinks }) => ( - <div className={ containerClassNames }> + <div className={ styles.container }> <Title title={ 'Share on Social' }/> <div className={ styles["container-inner"] }> - <span className={ buttonClassNames } data-share="facebook"> - <a className={ facebookClassNames } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook"> + <span className={ styles.button } data-share="facebook"> + <a className={ styles.facebook } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook"> Facebook <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> </span> - <span className={ buttonClassNames } data-share="twitter"> - <a className={ twitterClassNames } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter"> + <span className={ styles.button } data-share="twitter"> + <a className={ styles.twitter } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter"> Twitter <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> </span> - <span className={ buttonClassNames } data-share="linkedin"> - <a className={ linkedinClassNames } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin"> + <span className={ styles.button } data-share="linkedin"> + <a className={ styles.linkedin } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin"> LinkedIn <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> </span> - <span className={ buttonClassNames } data-share="whatsapp"> - <a className={ whatsappClassNames } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp"> + <span className={ styles.button } data-share="whatsapp"> + <a className={ styles.whatsapp } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp"> Whatsapp <span className={ styles["hidden-button-text"] }>(opens new window)</span> </a> </span> From cfb83b2259c4eef22ab066903bf8a9eb64190c99 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 12:07:39 +0000 Subject: [PATCH 338/760] Update tests --- .../__snapshots__/snapshots.test.js.snap | 469 ++++++++++-------- 1 file changed, 252 insertions(+), 217 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 5c78a90c1..28f7cdc27 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -5,13 +5,16 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a className="GiftArticle_container__2eZcF" > <form + className="GiftArticle_share-form__flrZ_" name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__3a4SZ" + <div + arialabelledby="gift-article-title" + role="group" > <div - className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" + className="GiftArticle_title__1i_Hv" + id="gift-article-title" > Share this article (free) </div> @@ -20,23 +23,25 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a data-section-id="nonGiftLink" data-trackable="nonGiftLink" > - <input - className="o-forms__text GiftArticle_url__bIcVi" - disabled={false} - name="non-gift-link" - readOnly={true} - type="text" - value="https://www.ft.com/content/blahblahblah?shareType=nongift" - /> + <span + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" + > + <input + className="GiftArticle_url-input__1vM0-" + disabled={false} + name="non-gift-link" + readOnly={true} + type="text" + value="https://www.ft.com/content/blahblahblah?shareType=nongift" + /> + </span> <div className="GiftArticle_message__3UjVE" > This article is currently - <span - className="GiftArticle_bold__2aMfD" - > + <strong> free - </span> + </strong> for anyone to read </div> <div @@ -53,7 +58,7 @@ exports[`@financial-times/x-gift-article renders a default Free article x-gift-a </a> </div> </div> - </fieldset> + </div> </form> </div> `; @@ -63,58 +68,63 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f className="GiftArticle_container__2eZcF" > <form + className="GiftArticle_share-form__flrZ_" name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__3a4SZ" + <div + arialabelledby="gift-article-title" + role="group" > <div - className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" + className="GiftArticle_title__1i_Hv" + id="gift-article-title" > Share this article (unable to fetch credits) </div> <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__qCYDb" + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> <label - className="o-forms__label" htmlFor="giftLink" > - with + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> <span - className="GiftArticle_bold__2aMfD" + className="GiftArticle_o-forms-input__label__1JFlX" > - anyone + with + <strong> + anyone + </strong> + (uses 1 gift credit) </span> - (uses 1 gift credit) </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> <label - className="o-forms__label" htmlFor="nonGiftLink" > - with + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> <span - className="GiftArticle_bold__2aMfD" + className="GiftArticle_o-forms-input__label__1JFlX" > - other FT subscribers + with + <strong> + other FT subscribers + </strong> </span> </label> </div> @@ -123,24 +133,26 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f data-section-id="giftLink" data-trackable="giftLink" > - <input - className="o-forms__text GiftArticle_url__bIcVi" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> + <span + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" + > + <input + className="GiftArticle_url-input__1vM0-" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + </span> <div className="GiftArticle_message__3UjVE" > You have - <span - className="GiftArticle_bold__2aMfD" - > + <strong> gift article credits - </span> + </strong> left this month </div> <div @@ -156,7 +168,7 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f </button> </div> </div> - </fieldset> + </div> </form> </div> `; @@ -166,58 +178,63 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g className="GiftArticle_container__2eZcF" > <form + className="GiftArticle_share-form__flrZ_" name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__3a4SZ" + <div + arialabelledby="gift-article-title" + role="group" > <div - className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" + className="GiftArticle_title__1i_Hv" + id="gift-article-title" > Share this article (with credit) </div> <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__qCYDb" + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> <label - className="o-forms__label" htmlFor="giftLink" > - with + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> <span - className="GiftArticle_bold__2aMfD" + className="GiftArticle_o-forms-input__label__1JFlX" > - anyone + with + <strong> + anyone + </strong> + (uses 1 gift credit) </span> - (uses 1 gift credit) </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> <label - className="o-forms__label" htmlFor="nonGiftLink" > - with + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> <span - className="GiftArticle_bold__2aMfD" + className="GiftArticle_o-forms-input__label__1JFlX" > - other FT subscribers + with + <strong> + other FT subscribers + </strong> </span> </label> </div> @@ -226,24 +243,26 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g data-section-id="giftLink" data-trackable="giftLink" > - <input - className="o-forms__text GiftArticle_url__bIcVi" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> + <span + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" + > + <input + className="GiftArticle_url-input__1vM0-" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + </span> <div className="GiftArticle_message__3UjVE" > You have - <span - className="GiftArticle_bold__2aMfD" - > + <strong> gift article credits - </span> + </strong> left this month </div> <div @@ -259,91 +278,93 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g </button> </div> </div> - </fieldset> + </div> </form> <div - className="o-share o-share--inverse MobileShareButtons_container__3eAtc" - data-o-component="o-share" + className="MobileShareButtons_container__3eAtc" > <div - className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" + className="GiftArticle_title__1i_Hv" + id="gift-article-title" > Share on Social </div> - <ul> - <li - className="o-share__action MobileShareButtons_button__17W-1" + <div + className="MobileShareButtons_container-inner__1Bpmj" + > + <span + className="MobileShareButtons_button__17W-1" data-share="facebook" > <a - className="o-share__icon o-share__icon--facebook MobileShareButtons_facebook__1ji2o" + className="MobileShareButtons_facebook__1ji2o" data-trackable="facebook" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" rel="noopener" > Facebook <span - className="o-share__text" + className="MobileShareButtons_hidden-button-text__3Uqb1" > (opens new window) </span> </a> - </li> - <li - className="o-share__action MobileShareButtons_button__17W-1" + </span> + <span + className="MobileShareButtons_button__17W-1" data-share="twitter" > <a - className="o-share__icon o-share__icon--twitter MobileShareButtons_twitter__1QRsw" + className="MobileShareButtons_twitter__1QRsw" data-trackable="twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" rel="noopener" > Twitter <span - className="o-share__text" + className="MobileShareButtons_hidden-button-text__3Uqb1" > (opens new window) </span> </a> - </li> - <li - className="o-share__action MobileShareButtons_button__17W-1" + </span> + <span + className="MobileShareButtons_button__17W-1" data-share="linkedin" > <a - className="o-share__icon o-share__icon--linkedin MobileShareButtons_linkedin__1-2-Y" + className="MobileShareButtons_linkedin__1-2-Y" data-trackable="linkedin" href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" rel="noopener" > LinkedIn <span - className="o-share__text" + className="MobileShareButtons_hidden-button-text__3Uqb1" > (opens new window) </span> </a> - </li> - <li - className="o-share__action MobileShareButtons_button__17W-1 o-share__action--whatsapp" + </span> + <span + className="MobileShareButtons_button__17W-1" data-share="whatsapp" > <a - className="o-share__icon o-share__icon--whatsapp MobileShareButtons_whatsapp__16VoZ" + className="MobileShareButtons_whatsapp__16VoZ" data-trackable="whatsapp" href="whatsapp://send?text=Title%20Title%20Title%20Title%20-%20https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" rel="noopener" > Whatsapp <span - className="o-share__text" + className="MobileShareButtons_hidden-button-text__3Uqb1" > (opens new window) </span> </a> - </li> - </ul> + </span> + </div> </div> </div> `; @@ -353,58 +374,63 @@ exports[`@financial-times/x-gift-article renders a default With native share on className="GiftArticle_container__2eZcF" > <form + className="GiftArticle_share-form__flrZ_" name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__3a4SZ" + <div + arialabelledby="gift-article-title" + role="group" > <div - className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" + className="GiftArticle_title__1i_Hv" + id="gift-article-title" > Share this article (on App) </div> <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__qCYDb" + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> <label - className="o-forms__label" htmlFor="giftLink" > - with + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> <span - className="GiftArticle_bold__2aMfD" + className="GiftArticle_o-forms-input__label__1JFlX" > - anyone + with + <strong> + anyone + </strong> + (uses 1 gift credit) </span> - (uses 1 gift credit) </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> <label - className="o-forms__label" htmlFor="nonGiftLink" > - with + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> <span - className="GiftArticle_bold__2aMfD" + className="GiftArticle_o-forms-input__label__1JFlX" > - other FT subscribers + with + <strong> + other FT subscribers + </strong> </span> </label> </div> @@ -413,24 +439,26 @@ exports[`@financial-times/x-gift-article renders a default With native share on data-section-id="giftLink" data-trackable="giftLink" > - <input - className="o-forms__text GiftArticle_url__bIcVi" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> + <span + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" + > + <input + className="GiftArticle_url-input__1vM0-" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + </span> <div className="GiftArticle_message__3UjVE" > You have - <span - className="GiftArticle_bold__2aMfD" - > + <strong> gift article credits - </span> + </strong> left this month </div> <div @@ -446,7 +474,7 @@ exports[`@financial-times/x-gift-article renders a default With native share on </button> </div> </div> - </fieldset> + </div> </form> </div> `; @@ -456,58 +484,63 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits className="GiftArticle_container__2eZcF" > <form + className="GiftArticle_share-form__flrZ_" name="gift-form" > - <fieldset - className="o-forms GiftArticle_form__3a4SZ" + <div + arialabelledby="gift-article-title" + role="group" > <div - className="GiftArticle_title__1i_Hv GiftArticle_bold__2aMfD" + className="GiftArticle_title__1i_Hv" + id="gift-article-title" > Share this article (without credit) </div> <div - className="o-forms__group o-forms__group--inline GiftArticle_radio-button-section__qCYDb" + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" > - <input - checked={true} - className="o-forms__radio" - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> <label - className="o-forms__label" htmlFor="giftLink" > - with + <input + checked={true} + className="o-forms__radio" + id="giftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="giftLink" + /> <span - className="GiftArticle_bold__2aMfD" + className="GiftArticle_o-forms-input__label__1JFlX" > - anyone + with + <strong> + anyone + </strong> + (uses 1 gift credit) </span> - (uses 1 gift credit) </label> - <input - checked={false} - className="o-forms__radio" - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> <label - className="o-forms__label" htmlFor="nonGiftLink" > - with + <input + checked={false} + className="o-forms__radio" + id="nonGiftLink" + name="gift-form__radio" + onChange={[Function]} + type="radio" + value="nonGiftLink" + /> <span - className="GiftArticle_bold__2aMfD" + className="GiftArticle_o-forms-input__label__1JFlX" > - other FT subscribers + with + <strong> + other FT subscribers + </strong> </span> </label> </div> @@ -516,24 +549,26 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits data-section-id="giftLink" data-trackable="giftLink" > - <input - className="o-forms__text GiftArticle_url__bIcVi" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> + <span + className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" + > + <input + className="GiftArticle_url-input__1vM0-" + disabled={true} + name="example-gift-link" + readOnly={true} + type="text" + value="https://on.ft.com/gift_link" + /> + </span> <div className="GiftArticle_message__3UjVE" > You have - <span - className="GiftArticle_bold__2aMfD" - > + <strong> gift article credits - </span> + </strong> left this month </div> <div @@ -549,7 +584,7 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits </button> </div> </div> - </fieldset> + </div> </form> </div> `; From 210c5c60929da85ca1bdf87b378621c61da4ba22 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 14:24:39 +0000 Subject: [PATCH 339/760] Update README --- components/x-gift-article/readme.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 6eecad9fb..6c5d78c1e 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -12,12 +12,8 @@ npm install --save @financial-times/x-gift-article ## Styling -To get correct styling, Your app should have origami components below. +To get correct styling, Your app should have: [o-fonts](https://registry.origami.ft.com/components/o-fonts) -[o-buttons](https://registry.origami.ft.com/components/o-buttons) -[o-forms](https://registry.origami.ft.com/components/o-forms) -[o-loading](https://registry.origami.ft.com/components/o-loading) -[o-message](https://registry.origami.ft.com/components/o-message) ## Usage From b97f4276bd7f8f394a16842a4e5ca9c6e1f09f68 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 15:39:07 +0000 Subject: [PATCH 340/760] Remove unused classes --- components/x-gift-article/src/RadioButtonsSection.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 6d8e563e4..1cfefbef9 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -17,7 +17,6 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( type="radio" name="gift-form__radio" value="giftLink" - className="o-forms__radio" id="giftLink" checked={ shareType === ShareType.gift } onChange={ showGiftUrlSection } @@ -32,7 +31,6 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( type="radio" name="gift-form__radio" value="nonGiftLink" - className="o-forms__radio" id="nonGiftLink" checked={ shareType === ShareType.nonGift } onChange={ showNonGiftUrlSection }/> From 563baf155a90d3ed6cfdbaece05638ba0bb3cb16 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 15:40:41 +0000 Subject: [PATCH 341/760] Update components/x-gift-article/src/Form.jsx Co-Authored-By: chee <chee@snoot.club> --- components/x-gift-article/src/Form.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index f3e6df08d..9ad9ee9ec 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -14,7 +14,7 @@ export default (props) => ( <div className={ styles.container }> <form name="gift-form" className={ formClassNames }> <div role="group" - arialabelledby="gift-article-title"> + arialabelledby="gift-article-title"> <Title title={ props.title }/> From 19f18830abfd1bf62e94ad2ce7919c2de2f8957a Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 15:40:47 +0000 Subject: [PATCH 342/760] Update components/x-gift-article/src/Buttons.jsx Co-Authored-By: chee <chee@snoot.club> --- components/x-gift-article/src/Buttons.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index e604cda4b..53aadc327 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -32,7 +32,7 @@ export default ({ type="button" onClick={ actions.shareByNativeShare }> Share link - </button> + </button> </div> ); } From cd308fd32fa3c198ce65d3aa7eb61182e3cf8c3d Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 15:40:54 +0000 Subject: [PATCH 343/760] Update components/x-gift-article/src/Buttons.jsx Co-Authored-By: chee <chee@snoot.club> --- components/x-gift-article/src/Buttons.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 53aadc327..4aa818edb 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -54,7 +54,7 @@ export default ({ rel="noopener noreferrer" onClick={ shareType === ShareType.gift ? actions.emailGiftUrl : actions.emailNonGiftUrl }> Email link - </a> + </a> </div> ); } From aa6163ebbfe6ff5c548456850554908d4a8edd12 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 15:52:19 +0000 Subject: [PATCH 344/760] Fix tests --- __tests__/__snapshots__/snapshots.test.js.snap | 8 -------- 1 file changed, 8 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 28f7cdc27..7d30f4fd6 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -89,7 +89,6 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f > <input checked={true} - className="o-forms__radio" id="giftLink" name="gift-form__radio" onChange={[Function]} @@ -111,7 +110,6 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f > <input checked={false} - className="o-forms__radio" id="nonGiftLink" name="gift-form__radio" onChange={[Function]} @@ -199,7 +197,6 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g > <input checked={true} - className="o-forms__radio" id="giftLink" name="gift-form__radio" onChange={[Function]} @@ -221,7 +218,6 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g > <input checked={false} - className="o-forms__radio" id="nonGiftLink" name="gift-form__radio" onChange={[Function]} @@ -395,7 +391,6 @@ exports[`@financial-times/x-gift-article renders a default With native share on > <input checked={true} - className="o-forms__radio" id="giftLink" name="gift-form__radio" onChange={[Function]} @@ -417,7 +412,6 @@ exports[`@financial-times/x-gift-article renders a default With native share on > <input checked={false} - className="o-forms__radio" id="nonGiftLink" name="gift-form__radio" onChange={[Function]} @@ -505,7 +499,6 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits > <input checked={true} - className="o-forms__radio" id="giftLink" name="gift-form__radio" onChange={[Function]} @@ -527,7 +520,6 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits > <input checked={false} - className="o-forms__radio" id="nonGiftLink" name="gift-form__radio" onChange={[Function]} From 82d1738c3c718a5141aa566830c67989bc3f7ae4 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 16:07:55 +0000 Subject: [PATCH 345/760] Make the $system-code variable defaultable --- components/x-gift-article/src/lib/variables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-gift-article/src/lib/variables.scss b/components/x-gift-article/src/lib/variables.scss index e76088485..74825af23 100644 --- a/components/x-gift-article/src/lib/variables.scss +++ b/components/x-gift-article/src/lib/variables.scss @@ -1,3 +1,3 @@ // This is needed for calls to the image service for Icons used in Social, // and oMessage -$system-code:'github:Financial-Times/x-dash'; +$system-code:'github:Financial-Times/x-dash' !default; From 28fc8981cb556c25555c3f21a12ec7551af7d45c Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Wed, 27 Nov 2019 16:29:49 +0000 Subject: [PATCH 346/760] Alphabetize dependencies --- components/x-teaser/storybook/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-teaser/storybook/index.js b/components/x-teaser/storybook/index.js index 216c17193..fd60a3d5a 100644 --- a/components/x-teaser/storybook/index.js +++ b/components/x-teaser/storybook/index.js @@ -5,11 +5,11 @@ exports.component = Teaser; exports.package = require('../package.json'); exports.dependencies = { - 'o-normalise': '^1.6.0', 'o-date': '^2.11.0', - 'o-typography': '^5.5.0', - 'o-teaser': '^3.5.0', 'o-labels': '^4.2.1', + 'o-normalise': '^1.6.0', + 'o-teaser': '^3.5.0', + 'o-typography': '^5.5.0', 'o-video': '^4.1.0' }; From 1a904adec858ebec7b738b9301271cc16c7241dd Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 28 Nov 2019 11:24:22 +0000 Subject: [PATCH 347/760] Test with latest origami versions x-teaser defines it's dependencies in storybook/index.js. Bumping these and then running the demos shows what things will break when consuming applications bump their versions to the latest releases. Only one thing looks to have broken as far as I can see, and that is that video now needs a system code to request the video. This commit adds a system code prop to Video, and sets it in the storybook code. Consuming applications will also need to set this prop. --- components/x-teaser/__fixtures__/video.json | 1 + components/x-teaser/src/Video.jsx | 3 ++- components/x-teaser/storybook/index.js | 12 ++++++------ components/x-teaser/storybook/video.js | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/components/x-teaser/__fixtures__/video.json b/components/x-teaser/__fixtures__/video.json index 5db90c9d9..e4182827f 100644 --- a/components/x-teaser/__fixtures__/video.json +++ b/components/x-teaser/__fixtures__/video.json @@ -8,6 +8,7 @@ "firstPublishedDate": "2018-03-26T08:12:28.137Z", "metaPrefixText": "", "metaSuffixText": "02:51min", + "systemCode": "x-teaser", "metaLink": { "url": "#", "prefLabel": "Global Trade" diff --git a/components/x-teaser/src/Video.jsx b/components/x-teaser/src/Video.jsx index eb68e8c50..621d3559a 100644 --- a/components/x-teaser/src/Video.jsx +++ b/components/x-teaser/src/Video.jsx @@ -22,7 +22,8 @@ const Embed = (props) => ( data-o-video-playsinline="true" data-o-video-placeholder="true" data-o-video-placeholder-info="[]" - data-o-video-placeholder-hint="Play video" /> + data-o-video-placeholder-hint="Play video" + data-o-video-systemcode={props.systemCode} /> </div> ); diff --git a/components/x-teaser/storybook/index.js b/components/x-teaser/storybook/index.js index fd60a3d5a..4f9e3d5e5 100644 --- a/components/x-teaser/storybook/index.js +++ b/components/x-teaser/storybook/index.js @@ -5,12 +5,12 @@ exports.component = Teaser; exports.package = require('../package.json'); exports.dependencies = { - 'o-date': '^2.11.0', - 'o-labels': '^4.2.1', - 'o-normalise': '^1.6.0', - 'o-teaser': '^3.5.0', - 'o-typography': '^5.5.0', - 'o-video': '^4.1.0' + 'o-date': '^4.0.0', + 'o-labels': '^5.0.0', + 'o-normalise': '^2.0.0', + 'o-teaser': '^4.0.0', + 'o-typography': '^6.0.0', + 'o-video': '^6.0.0' }; exports.stories = [ diff --git a/components/x-teaser/storybook/video.js b/components/x-teaser/storybook/video.js index bdb6db8fd..c655bb2f7 100644 --- a/components/x-teaser/storybook/video.js +++ b/components/x-teaser/storybook/video.js @@ -12,6 +12,7 @@ exports.knobs = [ 'id', 'url', 'type', + 'systemCode', // Meta 'showMeta', 'metaPrefixText', From 6269a6e84a6b6678edcb3bde76524106621f8c56 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 28 Nov 2019 11:32:54 +0000 Subject: [PATCH 348/760] Fix typo --- components/x-teaser/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index 92357a4a5..f49a020b7 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -220,7 +220,7 @@ Property | Type | Notes --------------|--------|-------------- `prefLabel` | String | `url` | String | Canonical URL -`relativeUrl` | String | URL path, will take precendence over `url` +`relativeUrl` | String | URL path, will take precedence over `url` #### Link Props @@ -228,7 +228,7 @@ Property | Type | Notes --------------|--------|------------------------------------------- `id` | String | Content UUID `url` | String | Canonical URL -`relativeUrl` | String | URL path, will take precendence over `url` +`relativeUrl` | String | URL path, will take precedence over `url` `type` | String | Content type (article, video, etc.) `title` | String | From 03261d8abc00c32acda45ded9eb4597e29cac614 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 28 Nov 2019 11:36:20 +0000 Subject: [PATCH 349/760] Document new prop --- components/x-teaser/readme.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index f49a020b7..d94be0eb9 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -185,10 +185,11 @@ Property | Type | Notes #### Video Props -Property | Type | Notes ----------|-----------------------|------------------------------------------------ -`video` | [media](#media-props) | Requires [o-video][ov] to create a video player - +Property | Type | Notes +-------------|-----------------------|------------------------------------------------ +`video` | [media](#media-props) | Requires [o-video][ov] to create a video player +`systemCode` | String | Required by o-video to pass with requests. + | | Should be the Biz-Ops code for the implementing system [ov]: https://github.com/Financial-Times/o-video #### Related Links Props From 4034dff54f7a9559e7fb75066afdc9b454d1c421 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 28 Nov 2019 11:43:38 +0000 Subject: [PATCH 350/760] Fix tests --- .../x-teaser/__tests__/__snapshots__/snapshots.test.js.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 54377026e..cfa25fd1b 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -1899,6 +1899,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` data-o-video-placeholder-hint="Play video" data-o-video-placeholder-info="[]" data-o-video-playsinline="true" + data-o-video-systemcode="x-teaser" /> </div> </div> From 2ab8e933e1b902fca9f0e95abcf5f5cd891dcff4 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Thu, 28 Nov 2019 12:41:02 +0000 Subject: [PATCH 351/760] Remove unneccesary variable --- components/x-gift-article/src/Form.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 9ad9ee9ec..7950fad05 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -6,13 +6,9 @@ import MobileShareButtons from './MobileShareButtons'; import CopyConfirmation from './CopyConfirmation'; import styles from './GiftArticle.scss'; -const formClassNames = [ - styles["share-form"] -].join(' '); - export default (props) => ( <div className={ styles.container }> - <form name="gift-form" className={ formClassNames }> + <form name="gift-form" className={ styles["share-form"] }> <div role="group" arialabelledby="gift-article-title"> From bf3342971bb1dc001856a4c8c3f5cd9388f46101 Mon Sep 17 00:00:00 2001 From: Alice Bartlett <alice.bartlett@gmail.com> Date: Fri, 29 Nov 2019 15:16:56 +0000 Subject: [PATCH 352/760] Remove unused demo This demo was broken by a change to x-handlebars and nobody noticed. Chatted it through with Bren and rather than fix it and then update the demo we decided we could probably delete this demo entirely as it's no longer needed. --- tools/x-ssr-demo/index.js | 58 ------------------------------- tools/x-ssr-demo/package.json | 38 -------------------- tools/x-ssr-demo/src/index.js | 20 ----------- tools/x-ssr-demo/views/index.html | 22 ------------ 4 files changed, 138 deletions(-) delete mode 100644 tools/x-ssr-demo/index.js delete mode 100644 tools/x-ssr-demo/package.json delete mode 100644 tools/x-ssr-demo/src/index.js delete mode 100644 tools/x-ssr-demo/views/index.html diff --git a/tools/x-ssr-demo/index.js b/tools/x-ssr-demo/index.js deleted file mode 100644 index 06dbdea4b..000000000 --- a/tools/x-ssr-demo/index.js +++ /dev/null @@ -1,58 +0,0 @@ -const express = require('express'); -const webpack = require('webpack'); -const webpackMiddleware = require('webpack-dev-middleware'); -const xEngine = require('@financial-times/x-engine/src/webpack'); -const getBabelConfig = require('@financial-times/x-babel-config'); -const path = require('path'); -const { Serialiser } = require('@financial-times/x-interaction'); - -const app = express(); -const publicPath = '/static'; - -require('@financial-times/n-handlebars')(app, { - directory: '.', - helpers: { - x: require('@financial-times/x-handlebars')(), - }, -}); - -const compiler = webpack({ - output: { - publicPath, - }, - plugins: [ - xEngine() - ], - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - include: path.join(__dirname, 'src'), - options: getBabelConfig(), - }, - ], - }, - mode: 'development', -}); - -app.use(webpackMiddleware(compiler, { - publicPath, - serverSideRender: true, -})); - -app.use((req, res) => { - const {assetsByChunkName} = res.locals.webpackStats.toJson(); - res.locals.serialiser = new Serialiser(); - - res.render('index', { - publicPath, - jsAssets: [].concat(assetsByChunkName.main).filter(path => path.endsWith('.js')), - cssAssets: [].concat(assetsByChunkName.main).filter(path => path.endsWith('.css')), - }); -}); - -app.listen(1370, () => { - /* eslint no-console:off */ - console.log('\nSSR demo listening on http://localhost:1370\n') -}); diff --git a/tools/x-ssr-demo/package.json b/tools/x-ssr-demo/package.json deleted file mode 100644 index f1e85d7ac..000000000 --- a/tools/x-ssr-demo/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@financial-times/x-ssr-demo", - "private": true, - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start": "nodemon -w index.js index.js" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@babel/core": "^7.4.3", - "@financial-times/n-handlebars": "^1.21.0", - "@financial-times/x-babel-config": "file:../../packages/x-babel-config", - "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-handlebars": "file:../../packages/x-handlebars", - "@financial-times/x-increment": "file:../../components/x-increment", - "@financial-times/x-interaction": "file:../../components/x-interaction", - "babel-core": "^6.0.0", - "babel-loader": "^8.0.5", - "express": "^4.16.3", - "hyperons": "^0.5.0", - "preact": "^10.0.0", - "webpack": "^4.8.1", - "webpack-dev-middleware": "^3.1.3" - }, - "x-dash": { - "engine": { - "server": "hyperons", - "browser": "preact" - } - }, - "devDependencies": { - "nodemon": "^1.17.4" - } -} diff --git a/tools/x-ssr-demo/src/index.js b/tools/x-ssr-demo/src/index.js deleted file mode 100644 index a2281a402..000000000 --- a/tools/x-ssr-demo/src/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import '@financial-times/x-increment'; -import {hydrate} from '@financial-times/x-interaction'; - -document.addEventListener('DOMContentLoaded', () => { - hydrate(); - - const external = document.getElementById('external-button'); - const increment = document.querySelector('[data-x-dash-id="x-ssr-increment-1"]'); - - external.addEventListener('click', () => { - increment.dispatchEvent(new CustomEvent('x-interaction.trigger-action', { - detail: { - action: 'increment', - args: [ { amount: 5 } ] - }, - })); - }); -}); - -import 'preact/devtools'; diff --git a/tools/x-ssr-demo/views/index.html b/tools/x-ssr-demo/views/index.html deleted file mode 100644 index 9e4e273b0..000000000 --- a/tools/x-ssr-demo/views/index.html +++ /dev/null @@ -1,22 +0,0 @@ -<!doctype html> -<html> -<head> - <title>x-dash SSR interactivity demo - {{#each cssAssets}} - - {{/each}} - - -
    - {{{x package="x-increment" component="Increment" id="x-ssr-increment-1" count=1 timeout=1000 serialiser=@root.serialiser}}} - - -
    - - {{#each jsAssets}} - - {{/each}} - - {{{x package="x-interaction" component="HydrationData" serialiser=@root.serialiser}}} - - From 6e46df766e8ec1ba98938a336127a3b5a5aad0cc Mon Sep 17 00:00:00 2001 From: Maggie Allen Date: Tue, 26 Nov 2019 15:03:38 +0000 Subject: [PATCH 353/760] add a diasableGuidance data-attribute to the Video.jsx template --- components/x-teaser/src/Video.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-teaser/src/Video.jsx b/components/x-teaser/src/Video.jsx index 621d3559a..7dd059ce7 100644 --- a/components/x-teaser/src/Video.jsx +++ b/components/x-teaser/src/Video.jsx @@ -20,6 +20,7 @@ const Embed = (props) => ( data-o-video-optimumvideowidth="640" data-o-video-autorender="true" data-o-video-playsinline="true" + data-o-video-disableGuidance={props.disableGuidance} data-o-video-placeholder="true" data-o-video-placeholder-info="[]" data-o-video-placeholder-hint="Play video" From 6af19db95f379883b14913310547ece228e17157 Mon Sep 17 00:00:00 2001 From: Maggie Allen Date: Thu, 28 Nov 2019 10:22:08 +0000 Subject: [PATCH 354/760] refactor Video template to use showGuidance over disableGuidance --- .../__tests__/__snapshots__/snapshots.test.js.snap | 1 + components/x-teaser/src/Video.jsx | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index cfa25fd1b..c241f4ef3 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -1899,6 +1899,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` data-o-video-placeholder-hint="Play video" data-o-video-placeholder-info="[]" data-o-video-playsinline="true" + data-o-video-show-guidance={true} data-o-video-systemcode="x-teaser" />
    diff --git a/components/x-teaser/src/Video.jsx b/components/x-teaser/src/Video.jsx index 7dd059ce7..0293c8ed9 100644 --- a/components/x-teaser/src/Video.jsx +++ b/components/x-teaser/src/Video.jsx @@ -9,7 +9,9 @@ const formatData = (props) => JSON.stringify({ // To prevent React from touching the DOM after mounting… return an empty
    // -const Embed = (props) => ( +const Embed = (props) => { + const showGuidance = typeof(props.showGuidance) === 'boolean' ? props.showGuidance : true; + return (
    ( data-o-video-optimumvideowidth="640" data-o-video-autorender="true" data-o-video-playsinline="true" - data-o-video-disableGuidance={props.disableGuidance} + data-o-video-show-guidance={showGuidance} data-o-video-placeholder="true" data-o-video-placeholder-info="[]" data-o-video-placeholder-hint="Play video" data-o-video-systemcode={props.systemCode} />
    -); + ) +}; export default (props) => (
    From fde15d9560c75117ff356d94923be9db014f1a66 Mon Sep 17 00:00:00 2001 From: Maggie Allen Date: Thu, 28 Nov 2019 10:22:52 +0000 Subject: [PATCH 355/760] document showGuidance option in the Feature Props list --- components/x-teaser/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index d94be0eb9..aa6c27a59 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -117,6 +117,7 @@ Feature | Type | Notes `showImage` | Boolean | `showHeadshot` | Boolean | Takes precedence over image `showVideo` | Boolean | Takes precedence over image or headshot +`showGuidance` | Boolean | Show video captions guidance `showRelatedLinks` | Boolean | `showCustomSlot` | Boolean | From 78541a29e1a16e92f7bab993128c3c41b6d2ed71 Mon Sep 17 00:00:00 2001 From: Maggie Allen Date: Mon, 2 Dec 2019 08:58:02 +0000 Subject: [PATCH 356/760] make showGuidance attribute a string --- .../x-teaser/__tests__/__snapshots__/snapshots.test.js.snap | 2 +- components/x-teaser/src/Video.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index c241f4ef3..e0bbf34d3 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -1899,7 +1899,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = ` data-o-video-placeholder-hint="Play video" data-o-video-placeholder-info="[]" data-o-video-playsinline="true" - data-o-video-show-guidance={true} + data-o-video-show-guidance="true" data-o-video-systemcode="x-teaser" />
    diff --git a/components/x-teaser/src/Video.jsx b/components/x-teaser/src/Video.jsx index 0293c8ed9..9d35ad912 100644 --- a/components/x-teaser/src/Video.jsx +++ b/components/x-teaser/src/Video.jsx @@ -10,7 +10,7 @@ const formatData = (props) => JSON.stringify({ // To prevent React from touching the DOM after mounting… return an empty
    // const Embed = (props) => { - const showGuidance = typeof(props.showGuidance) === 'boolean' ? props.showGuidance : true; + const showGuidance = typeof props.showGuidance === 'boolean' ? props.showGuidance.toString() : "true"; return (
    Date: Mon, 2 Dec 2019 12:01:28 +0000 Subject: [PATCH 357/760] Update dependency delaunator to v4 --- packages/x-logo/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-logo/package.json b/packages/x-logo/package.json index 0af98f157..873b62752 100644 --- a/packages/x-logo/package.json +++ b/packages/x-logo/package.json @@ -8,7 +8,7 @@ "author": "", "license": "ISC", "dependencies": { - "delaunator": "^2.0.0", + "delaunator": "^4.0.0", "hsluv": "^0.0.3", "point-in-polygon": "^1.0.1", "poisson-disk-sampling": "^1.0.2", From 4ae4119ae605d5b25d51523d7ac5063c2afa5bc3 Mon Sep 17 00:00:00 2001 From: Edd Sowden Date: Wed, 4 Dec 2019 13:44:39 +0000 Subject: [PATCH 358/760] Use sass-loader in storybook webpack So that we can use Sass in components. This also adds all the bower paths to the sass load path. Which works for the moment but might need making more robust in the future. --- .storybook/webpack.config.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index b419c439b..70520a4c8 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -2,6 +2,7 @@ // See https://storybook.js.org/configurations/custom-webpack-config/ for more info. const path = require('path'); +const glob = require('glob'); const fs = require('fs'); const xBabelConfig = require('../packages/x-babel-config'); const xEngine = require('../packages/x-engine/src/webpack'); @@ -58,6 +59,29 @@ module.exports = ({ config }) => { return name.includes('babel-preset-minify') === false; }); + config.module.rules.push({ + test: /\.(scss|sass)$/, + use: [ + { + loader: require.resolve('style-loader') + }, + { + loader: require.resolve('css-loader'), + options: { + url: false, + import: false, + modules: true + } + }, + { + loader: require.resolve('sass-loader'), + options: { + includePaths: glob.sync('./components/*/bower_components', { absolute: true }), + } + } + ] + }); + // HACK: Ensure we only bundle one instance of React config.resolve.alias.react = require.resolve('react'); From f7474c7f4dfd04a171dfff7ddf2eaba810c9385f Mon Sep 17 00:00:00 2001 From: Edd Sowden Date: Wed, 4 Dec 2019 14:26:04 +0000 Subject: [PATCH 359/760] Update x-follow-button for origami major change So that we can include it in projects that use other components that use the latest changes. --- components/x-follow-button/bower.json | 6 +-- .../x-follow-button/src/styles/main.scss | 5 ++- .../src/styles/mixins/lozenge/_themes.scss | 38 +++++++++---------- .../styles/mixins/lozenge/_toggle-icon.scss | 4 +- .../src/styles/mixins/lozenge/main.scss | 2 +- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/components/x-follow-button/bower.json b/components/x-follow-button/bower.json index 08ca23073..48cc79ee1 100644 --- a/components/x-follow-button/bower.json +++ b/components/x-follow-button/bower.json @@ -3,8 +3,8 @@ "private": true, "main": "dist/FollowButton.es5.js", "dependencies": { - "o-colors": "^4.4.1", - "o-icons": "^5.7.1", - "o-typography": "^5.7.4" + "o-colors": "^5.0.3", + "o-icons": "^6.0.0", + "o-typography": "^6.1.0" } } diff --git a/components/x-follow-button/src/styles/main.scss b/components/x-follow-button/src/styles/main.scss index 19b50ddcd..529d088fa 100644 --- a/components/x-follow-button/src/styles/main.scss +++ b/components/x-follow-button/src/styles/main.scss @@ -1,7 +1,10 @@ +// TODO: update me to not need a system code +$system-code:'github:Financial-Times/x-dash' !default; + @import 'o-icons/main'; @import 'o-colors/main'; @import 'o-typography/main'; @import './mixins/lozenge/main.scss'; -@import './components/FollowButton.scss'; \ No newline at end of file +@import './components/FollowButton.scss'; diff --git a/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss b/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss index 38cfe434a..c144e52ee 100644 --- a/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss +++ b/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss @@ -2,32 +2,32 @@ $theme-map: null; $myft-lozenge-themes: ( standard: ( - background: oColorsGetPaletteColor('claret'), - text: oColorsGetPaletteColor('white'), - highlight: oColorsGetPaletteColor('claret-50'), - pressed-highlight: rgba(oColorsGetPaletteColor('black'), 0.05), - disabled: rgba(oColorsGetPaletteColor('black'), 0.5) + background: oColorsByName('claret'), + text: oColorsByName('white'), + highlight: oColorsByName('claret-50'), + pressed-highlight: rgba(oColorsByName('black'), 0.05), + disabled: rgba(oColorsByName('black'), 0.5) ), inverse: ( - background: oColorsGetPaletteColor('white'), - text: oColorsGetPaletteColor('claret'), + background: oColorsByName('white'), + text: oColorsByName('claret'), highlight: rgba(white, 0.8), pressed-highlight: rgba(white, 0.2), - disabled: rgba(oColorsGetPaletteColor('white'), 0.5) + disabled: rgba(oColorsByName('white'), 0.5) ), opinion: ( - background: oColorsGetPaletteColor('oxford-40'), - text: oColorsGetPaletteColor('white'), - highlight: oColorsGetPaletteColor('oxford-30'), - pressed-highlight: rgba(oColorsGetPaletteColor('oxford-40'), 0.2), - disabled: rgba(oColorsGetPaletteColor('black'), 0.5) + background: oColorsByName('oxford-40'), + text: oColorsByName('white'), + highlight: oColorsByName('oxford-30'), + pressed-highlight: rgba(oColorsByName('oxford-40'), 0.2), + disabled: rgba(oColorsByName('black'), 0.5) ), monochrome: ( - background: oColorsGetPaletteColor('white'), - text: oColorsGetPaletteColor('black'), - highlight: oColorsGetPaletteColor('white-80'), - pressed-highlight: rgba(oColorsGetPaletteColor('white'), 0.2), - disabled: rgba(oColorsGetPaletteColor('white'), 0.5) + background: oColorsByName('white'), + text: oColorsByName('black'), + highlight: oColorsByName('white-80'), + pressed-highlight: rgba(oColorsByName('white'), 0.2), + disabled: rgba(oColorsByName('white'), 0.5) ) ); @@ -39,4 +39,4 @@ $myft-lozenge-themes: ( $theme-map: map-get($myft-lozenge-themes, $theme) !global; @content; -} \ No newline at end of file +} diff --git a/components/x-follow-button/src/styles/mixins/lozenge/_toggle-icon.scss b/components/x-follow-button/src/styles/mixins/lozenge/_toggle-icon.scss index c5e3613f0..302298c94 100644 --- a/components/x-follow-button/src/styles/mixins/lozenge/_toggle-icon.scss +++ b/components/x-follow-button/src/styles/mixins/lozenge/_toggle-icon.scss @@ -1,5 +1,5 @@ @mixin getIcon($name, $color) { - @include oIconsGetIcon($icon-name: $name, $container-width: 10, $color: $color, $iconset-version: 1); + @include oIconsContent($icon-name: $name, $size: 10, $color: $color, $iconset-version: 1); content: ''; } @@ -40,4 +40,4 @@ } } } -} \ No newline at end of file +} diff --git a/components/x-follow-button/src/styles/mixins/lozenge/main.scss b/components/x-follow-button/src/styles/mixins/lozenge/main.scss index 1c20fffe2..c0a0d854a 100644 --- a/components/x-follow-button/src/styles/mixins/lozenge/main.scss +++ b/components/x-follow-button/src/styles/mixins/lozenge/main.scss @@ -42,7 +42,7 @@ @mixin myftLozenge($theme: standard, $with-toggle-icon: false) { @include myftLozengeTheme($theme, $with-toggle-icon); - @include oTypographySansBold($scale: -1); + @include oTypographySans($scale: -1, $weight: 'semibold'); border-radius: 100px; // Number that will be larger than any possible height, so that works for all possible button sizes box-sizing: content-box; From 025266af8f48d0b553aa7cf8996b90124f39974a Mon Sep 17 00:00:00 2001 From: bren Date: Thu, 5 Dec 2019 16:32:28 +0000 Subject: [PATCH 360/760] don't build before test --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index d8e13a30c..8297427d7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "build": "athloi run build --concurrency 3 && npm run build-storybook", "build-only": "athloi run build", "jest": "jest -c jest.config.js", - "pretest": "npm run build", "test": "npm run lint && npm run jest", "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", From 17a5d7c0b3697486097ff039be43296010788c0f Mon Sep 17 00:00:00 2001 From: bren Date: Thu, 5 Dec 2019 16:32:49 +0000 Subject: [PATCH 361/760] refactor image service helper to use URL constructor --- components/x-teaser/src/Headshot.jsx | 5 ++++- .../x-teaser/src/concerns/image-service.js | 22 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/components/x-teaser/src/Headshot.jsx b/components/x-teaser/src/Headshot.jsx index 0e7fe4fac..05ab4ae10 100644 --- a/components/x-teaser/src/Headshot.jsx +++ b/components/x-teaser/src/Headshot.jsx @@ -5,7 +5,10 @@ import imageService from './concerns/image-service'; const DEFAULT_TINT = '054593,d6d5d3'; export default ({ headshot, headshotTint }) => { - const options = [`tint=${DEFAULT_TINT || headshotTint}`, 'dpr=2'].join('&'); + const options = { + tint: DEFAULT_TINT || headshotTint, + dpr: 2 + }; return headshot ? ( Date: Thu, 5 Dec 2019 16:34:24 +0000 Subject: [PATCH 362/760] default to medium quality 2x images --- .../__snapshots__/snapshots.test.js.snap | 82 +++++++++---------- components/x-teaser/src/Headshot.jsx | 1 - .../x-teaser/src/concerns/image-service.js | 3 +- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index e0bbf34d3..0066b5d23 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -52,7 +52,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = `
    @@ -172,7 +172,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`]
    @@ -232,7 +232,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = `
    @@ -297,7 +297,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] =
    @@ -363,7 +363,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = `
    @@ -425,7 +425,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = `
    @@ -485,7 +485,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = `
    @@ -551,7 +551,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = `
    @@ -1053,7 +1053,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`]
    @@ -1173,7 +1173,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d
    @@ -1233,7 +1233,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`]
    @@ -1298,7 +1298,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data
    @@ -1364,7 +1364,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`]
    @@ -1426,7 +1426,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1`
    @@ -1486,7 +1486,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1`
    @@ -1552,7 +1552,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] =
    @@ -1927,7 +1927,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = `
    @@ -2014,7 +2014,7 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = `
    @@ -2158,7 +2158,7 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1`
    @@ -2230,7 +2230,7 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = `
    @@ -2307,7 +2307,7 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] =
    @@ -2385,7 +2385,7 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = `
    @@ -2459,7 +2459,7 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = `
    @@ -2531,7 +2531,7 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = `
    @@ -2609,7 +2609,7 @@ exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = `
    @@ -3015,7 +3015,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`]
    @@ -3159,7 +3159,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da
    @@ -3231,7 +3231,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`]
    @@ -3308,7 +3308,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data
    @@ -3386,7 +3386,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`]
    @@ -3460,7 +3460,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`]
    @@ -3532,7 +3532,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`]
    @@ -3610,7 +3610,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] =
    @@ -4161,7 +4161,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da
    @@ -4305,7 +4305,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac
    @@ -4377,7 +4377,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da
    @@ -4454,7 +4454,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte
    @@ -4532,7 +4532,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da
    @@ -4606,7 +4606,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d
    @@ -4678,7 +4678,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d
    @@ -4793,7 +4793,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with video data
    diff --git a/components/x-teaser/src/Headshot.jsx b/components/x-teaser/src/Headshot.jsx index 05ab4ae10..58511a0f7 100644 --- a/components/x-teaser/src/Headshot.jsx +++ b/components/x-teaser/src/Headshot.jsx @@ -7,7 +7,6 @@ const DEFAULT_TINT = '054593,d6d5d3'; export default ({ headshot, headshotTint }) => { const options = { tint: DEFAULT_TINT || headshotTint, - dpr: 2 }; return headshot ? ( diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index c6d339d63..d92c0af0b 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -4,7 +4,8 @@ const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw/'; const DEFAULT_OPTIONS = { source: 'next', fit: 'scale-down', - compression: 'best' + quality: 'medium', + dpr: 2 }; /** From 903b30d86a0f422571beb5adc54683c10a851314 Mon Sep 17 00:00:00 2001 From: bren Date: Thu, 5 Dec 2019 16:41:23 +0000 Subject: [PATCH 363/760] fix passing in headshot tint while i'm here --- components/x-teaser/src/Headshot.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/src/Headshot.jsx b/components/x-teaser/src/Headshot.jsx index 58511a0f7..4c055c8a5 100644 --- a/components/x-teaser/src/Headshot.jsx +++ b/components/x-teaser/src/Headshot.jsx @@ -6,7 +6,7 @@ const DEFAULT_TINT = '054593,d6d5d3'; export default ({ headshot, headshotTint }) => { const options = { - tint: DEFAULT_TINT || headshotTint, + tint: headshotTint || DEFAULT_TINT, }; return headshot ? ( From 43213373b6ad84b994422e43906aa150577c647f Mon Sep 17 00:00:00 2001 From: bren Date: Thu, 5 Dec 2019 16:43:11 +0000 Subject: [PATCH 364/760] use global URL for browser compat --- components/x-teaser/src/concerns/image-service.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index d92c0af0b..e6bf74573 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -1,5 +1,3 @@ -const { URL, URLSearchParams } = require('url'); - const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw/'; const DEFAULT_OPTIONS = { source: 'next', From f973410b4b5ab8b92a7972e0b35b8210a41620f1 Mon Sep 17 00:00:00 2001 From: bren Date: Thu, 5 Dec 2019 17:00:56 +0000 Subject: [PATCH 365/760] explanatory headshot tint comment --- components/x-teaser/src/Headshot.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-teaser/src/Headshot.jsx b/components/x-teaser/src/Headshot.jsx index 4c055c8a5..96887ad5f 100644 --- a/components/x-teaser/src/Headshot.jsx +++ b/components/x-teaser/src/Headshot.jsx @@ -2,6 +2,7 @@ import { h } from '@financial-times/x-engine'; import { ImageSizes } from './concerns/constants'; import imageService from './concerns/image-service'; +// these colours are tweaked from o-colors palette colours to make headshots look less washed out const DEFAULT_TINT = '054593,d6d5d3'; export default ({ headshot, headshotTint }) => { From 034589aff4b8070ab58e8c3ad48b439aa8fa22a9 Mon Sep 17 00:00:00 2001 From: bren Date: Thu, 5 Dec 2019 17:14:59 +0000 Subject: [PATCH 366/760] don't specify medium, it's the default quality --- .../__snapshots__/snapshots.test.js.snap | 82 +++++++++---------- .../x-teaser/src/concerns/image-service.js | 1 - 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 0066b5d23..f35854e50 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -52,7 +52,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = `
    @@ -172,7 +172,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`]
    @@ -232,7 +232,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = `
    @@ -297,7 +297,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] =
    @@ -363,7 +363,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = `
    @@ -425,7 +425,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = `
    @@ -485,7 +485,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = `
    @@ -551,7 +551,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = `
    @@ -1053,7 +1053,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`]
    @@ -1173,7 +1173,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d
    @@ -1233,7 +1233,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`]
    @@ -1298,7 +1298,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data
    @@ -1364,7 +1364,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`]
    @@ -1426,7 +1426,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1`
    @@ -1486,7 +1486,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1`
    @@ -1552,7 +1552,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] =
    @@ -1927,7 +1927,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = `
    @@ -2014,7 +2014,7 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = `
    @@ -2158,7 +2158,7 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1`
    @@ -2230,7 +2230,7 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = `
    @@ -2307,7 +2307,7 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] =
    @@ -2385,7 +2385,7 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = `
    @@ -2459,7 +2459,7 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = `
    @@ -2531,7 +2531,7 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = `
    @@ -2609,7 +2609,7 @@ exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = `
    @@ -3015,7 +3015,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`]
    @@ -3159,7 +3159,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da
    @@ -3231,7 +3231,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`]
    @@ -3308,7 +3308,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data
    @@ -3386,7 +3386,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`]
    @@ -3460,7 +3460,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`]
    @@ -3532,7 +3532,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`]
    @@ -3610,7 +3610,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] =
    @@ -4161,7 +4161,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da
    @@ -4305,7 +4305,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac
    @@ -4377,7 +4377,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da
    @@ -4454,7 +4454,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte
    @@ -4532,7 +4532,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da
    @@ -4606,7 +4606,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d
    @@ -4678,7 +4678,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d
    @@ -4793,7 +4793,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with video data
    diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index e6bf74573..edf826c18 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -2,7 +2,6 @@ const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw/'; const DEFAULT_OPTIONS = { source: 'next', fit: 'scale-down', - quality: 'medium', dpr: 2 }; From 3ec46022f4922a51a4e256b1eeb1cc8ccdc29b05 Mon Sep 17 00:00:00 2001 From: bren Date: Fri, 6 Dec 2019 11:27:14 +0000 Subject: [PATCH 367/760] Revert "refactor image service helper to use URL constructor" Global URL was only added in node v10 so we can't use it This reverts commit 17a5d7c0b3697486097ff039be43296010788c0f. --- components/x-teaser/src/Headshot.jsx | 4 +--- .../x-teaser/src/concerns/image-service.js | 20 +++++-------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/components/x-teaser/src/Headshot.jsx b/components/x-teaser/src/Headshot.jsx index 96887ad5f..cbe832468 100644 --- a/components/x-teaser/src/Headshot.jsx +++ b/components/x-teaser/src/Headshot.jsx @@ -6,9 +6,7 @@ import imageService from './concerns/image-service'; const DEFAULT_TINT = '054593,d6d5d3'; export default ({ headshot, headshotTint }) => { - const options = { - tint: headshotTint || DEFAULT_TINT, - }; + const options = `tint=${headshotTint || DEFAULT_TINT}`; return headshot ? ( Date: Fri, 6 Dec 2019 11:41:21 +0000 Subject: [PATCH 368/760] Update dependency rollup to v1 --- components/x-follow-button/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-follow-button/package.json b/components/x-follow-button/package.json index a873b6f42..ff77c0435 100644 --- a/components/x-follow-button/package.json +++ b/components/x-follow-button/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@financial-times/x-rollup": "file:../../packages/x-rollup", "@financial-times/x-test-utils": "file:../../packages/x-test-utils", - "rollup": "^0.57.1", + "rollup": "^1.0.0", "bower": "^1.7.9", "node-sass": "^4.9.2" }, From 3af4d11fb9c84bf244870101bb5cf3745d7b4f57 Mon Sep 17 00:00:00 2001 From: Edd Sowden Date: Mon, 30 Dec 2019 16:31:41 +0000 Subject: [PATCH 369/760] Adding github settings.yml This lets us centrally define access permissions to our repositories and other sensible defaults where approrpriate. You can see the defaults in the [github-apps-config-next][defaults] repo. For full configuration options check out the [probot settings repo][probot]. [defaults]: https://github.com/Financial-Times/github-apps-config-next/blob/master/.github/settings.yml [probot]: https://github.com/probot/settings --- .github/settings.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/settings.yml diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 000000000..a1f30bc26 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,17 @@ +_extends: github-apps-config-next + +branches: + - name: master + protection: + required_pull_request_reviews: + required_approving_review_count: 1 + dismiss_stale_reviews: true + require_code_owner_reviews: false + required_status_checks: + strict: true + contexts: + - "ci/circleci: test" + enforce_admins: false + restrictions: + users: [] + teams: [] From be70aeea61c4fd8d38f43a138c600dbfd7b9528c Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Mon, 6 Jan 2020 12:05:31 +0000 Subject: [PATCH 370/760] Remove rollup dependency from x-follow-button component --- components/x-follow-button/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/components/x-follow-button/package.json b/components/x-follow-button/package.json index ff77c0435..78abb478d 100644 --- a/components/x-follow-button/package.json +++ b/components/x-follow-button/package.json @@ -17,7 +17,6 @@ "devDependencies": { "@financial-times/x-rollup": "file:../../packages/x-rollup", "@financial-times/x-test-utils": "file:../../packages/x-test-utils", - "rollup": "^1.0.0", "bower": "^1.7.9", "node-sass": "^4.9.2" }, From fd0a6b6992a5651fceeecd7f2aaafe8b4d742919 Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 15 Jan 2020 09:59:22 +0000 Subject: [PATCH 371/760] Adds test for transform.js module --- .../__tests__/lib/transform.test.js | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 components/x-teaser-timeline/__tests__/lib/transform.test.js diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js new file mode 100644 index 000000000..136227496 --- /dev/null +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -0,0 +1,159 @@ +import { getItemGroups } from '../../src/lib/transform'; + +const props = { + 'items': [ + { + 'id': '01f0b004-36b9-11ea-a6d3-9a26f8c3cba4', + 'title': 'Europeans step up pressure on Iran over nuclear deal', + 'publishedDate': '2020-01-14T11:10:26.000Z' + }, + { + 'id': '01eaf2fc-36ac-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran’s judiciary threatens to expel UK ambassador', + 'publishedDate': '2020-01-14T10:23:14.000Z' + }, + { + 'id': 'dcac61ea-361c-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran’s regime loses the battle for public opinion', + 'publishedDate': '2020-01-14T10:00:27.000Z' + }, + { + 'id': 'bf0752ee-3685-11ea-a6d3-9a26f8c3cba4', + 'title': 'Justin Trudeau partly blames US for Iran plane crash', + 'publishedDate': '2020-01-14T08:15:05.000Z' + }, + { + 'id': '1d1527fa-356c-11ea-a6d3-9a26f8c3cba4', + 'title': 'Biden and Sanders reprise Iraq war fight in 2020 race', + 'publishedDate': '2020-01-13T11:00:26.000Z' + }, + { + 'id': '6524b530-355b-11ea-a6d3-9a26f8c3cba4', + 'title': 'Esper ‘didn’t see’ specific evidence of embassy threat', + 'publishedDate': '2020-01-12T18:36:39.000Z' + }, + { + 'id': '86df67f6-3524-11ea-a6d3-9a26f8c3cba4', + 'title': 'Lies over air crash shake Iran’s trust in its rulers', + 'publishedDate': '2020-01-12T16:29:11.000Z' + }, + { + 'id': '37084c8c-3508-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran questions Revolutionary Guard over downing of airliner', + 'publishedDate': '2020-01-12T09:51:13.000Z' + }, + { + 'id': 'b931cc22-3073-11ea-a329-0bcf87a328f2', + 'title': 'What next for oil as US-Iran tensions simmer?', + 'publishedDate': '2020-01-12T09:00:26.000Z' + }, + { + 'id': '0e76e39a-3428-11ea-9703-eea0cae3f0de', + 'title': 'Iran admits it shot down Ukrainian jet', + 'publishedDate': '2020-01-12T05:35:29.000Z' + } + ], + 'showSaveButtons': false, + 'latestItemsTime': null, + 'customSlotContent': { 'children': [], 'attributes': { 'shouldRender': false } }, + 'customSlotPosition': 2, + 'children': [] +}; +const expected = [ + { + 'date': '2020-01-14', + 'title': 'Earlier Today', + 'items': [ + { + 'articleIndex': 0, + 'localisedLastUpdated': '2020-01-14T11:10:26.000+00:00', + 'id': '01f0b004-36b9-11ea-a6d3-9a26f8c3cba4', + 'title': 'Europeans step up pressure on Iran over nuclear deal', + 'publishedDate': '2020-01-14T11:10:26.000Z' + }, + { + 'articleIndex': 1, + 'localisedLastUpdated': '2020-01-14T10:23:14.000+00:00', + 'id': '01eaf2fc-36ac-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran’s judiciary threatens to expel UK ambassador', + 'publishedDate': '2020-01-14T10:23:14.000Z' + }, + { + 'articleIndex': 2, + 'localisedLastUpdated': '2020-01-14T10:00:27.000+00:00', + 'id': 'dcac61ea-361c-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran’s regime loses the battle for public opinion', + 'publishedDate': '2020-01-14T10:00:27.000Z' + }, + { + 'articleIndex': 3, + 'localisedLastUpdated': '2020-01-14T08:15:05.000+00:00', + 'id': 'bf0752ee-3685-11ea-a6d3-9a26f8c3cba4', + 'title': 'Justin Trudeau partly blames US for Iran plane crash', + 'publishedDate': '2020-01-14T08:15:05.000Z' + } + ] + }, + { + 'date': '2020-01-13', + 'title': 'Yesterday', + 'items': + [ + { + 'articleIndex': 4, + 'localisedLastUpdated': '2020-01-13T11:00:26.000+00:00', + 'id': '1d1527fa-356c-11ea-a6d3-9a26f8c3cba4', + 'title': 'Biden and Sanders reprise Iraq war fight in 2020 race', + 'publishedDate': '2020-01-13T11:00:26.000Z' + } + ] + }, + { + 'date': '2020-01-12', + 'title': 'January 12, 2020', + 'items': [ + { + 'articleIndex': 5, + 'localisedLastUpdated': '2020-01-12T18:36:39.000+00:00', + 'id': '6524b530-355b-11ea-a6d3-9a26f8c3cba4', + 'title': 'Esper ‘didn’t see’ specific evidence of embassy threat', + 'publishedDate': '2020-01-12T18:36:39.000Z' + }, + { + 'articleIndex': 6, + 'localisedLastUpdated': '2020-01-12T16:29:11.000+00:00', + 'id': '86df67f6-3524-11ea-a6d3-9a26f8c3cba4', + 'title': 'Lies over air crash shake Iran’s trust in its rulers', + 'publishedDate': '2020-01-12T16:29:11.000Z' + }, + { + 'articleIndex': 7, + 'localisedLastUpdated': '2020-01-12T09:51:13.000+00:00', + 'id': '37084c8c-3508-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran questions Revolutionary Guard over downing of airliner', + 'publishedDate': '2020-01-12T09:51:13.000Z' + }, + { + 'articleIndex': 8, + 'localisedLastUpdated': '2020-01-12T09:00:26.000+00:00', + 'id': 'b931cc22-3073-11ea-a329-0bcf87a328f2', + 'title': 'What next for oil as US-Iran tensions simmer?', + 'publishedDate': '2020-01-12T09:00:26.000Z' + }, + { + 'articleIndex': 9, + 'localisedLastUpdated': '2020-01-12T05:35:29.000+00:00', + 'id': '0e76e39a-3428-11ea-9703-eea0cae3f0de', + 'title': 'Iran admits it shot down Ukrainian jet', + 'publishedDate': '2020-01-12T05:35:29.000Z' + } + ] + } +]; + +describe.only('groupItemsByLocalisedDate', () => { + test('correctly groups some stuff', () => { + const result = getItemGroups({ ...props, timezoneOffset: 0, localTodayDate: '2020-01-14' }); + expect(result).toEqual(expected); + }); +}); From 178c270d2d8e43d37aa5b15f089a59d694535e6d Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 15 Jan 2020 10:00:53 +0000 Subject: [PATCH 372/760] Don't export unnecessarily To limit API surface prior to refactoring --- components/x-teaser-timeline/src/lib/transform.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index 1b70e866f..7115bde20 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -4,9 +4,9 @@ import { splitLatestEarlier } from './date'; -export const getDateOnly = date => date.substr(0, 10); +const getDateOnly = date => date.substr(0, 10); -export const groupItemsByLocalisedDate = (items, timezoneOffset) => { +const groupItemsByLocalisedDate = (items, timezoneOffset) => { const itemsByLocalisedDate = {}; items.forEach((item, index) => { @@ -27,7 +27,7 @@ export const groupItemsByLocalisedDate = (items, timezoneOffset) => { })); }; -export const splitTodaysItems = (itemGroups, localTodayDate, latestItemsTime) => { +const splitTodaysItems = (itemGroups, localTodayDate, latestItemsTime) => { const firstGroupIsToday = itemGroups[0] && itemGroups[0].date === localTodayDate; const latestTimeIsToday = getDateOnly(latestItemsTime) === localTodayDate; @@ -59,7 +59,7 @@ export const splitTodaysItems = (itemGroups, localTodayDate, latestItemsTime) => return itemGroups; }; -export const addItemGroupTitles = (itemGroups, localTodayDate) => { +const addItemGroupTitles = (itemGroups, localTodayDate) => { return itemGroups.map(group => { group.title = getTitleForItemGroup(group.date, localTodayDate); From 9021761ca19eee856b1803dfd3c92a43f39a0f33 Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 15 Jan 2020 10:39:16 +0000 Subject: [PATCH 373/760] Adds test for getGroupAndIndex --- .../__tests__/lib/transform.test.js | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js index 136227496..de3ebcd0f 100644 --- a/components/x-teaser-timeline/__tests__/lib/transform.test.js +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -1,4 +1,4 @@ -import { getItemGroups } from '../../src/lib/transform'; +import { getItemGroups, getGroupAndIndex } from '../../src/lib/transform'; const props = { 'items': [ @@ -59,7 +59,7 @@ const props = { 'customSlotPosition': 2, 'children': [] }; -const expected = [ +const itemGroups = [ { 'date': '2020-01-14', 'title': 'Earlier Today', @@ -151,9 +151,36 @@ const expected = [ } ]; -describe.only('groupItemsByLocalisedDate', () => { +describe.only('getItemGroups', () => { test('correctly groups some stuff', () => { const result = getItemGroups({ ...props, timezoneOffset: 0, localTodayDate: '2020-01-14' }); - expect(result).toEqual(expected); + expect(result).toEqual(itemGroups); + }); +}); + +describe.only('getGroupAndIndex', () => { + test('returns correct group and index for middle of first group', () => { + const insertPosition = 2; + const result = getGroupAndIndex( itemGroups, insertPosition ); + expect(result.group).toBe(0); + expect(result.index).toBe(2); + }); + test('returns correct group and index for end of second group', () => { + const insertPosition = 5; + const result = getGroupAndIndex( itemGroups, insertPosition ); + expect(result.group).toBe(1); + expect(result.index).toBe(1); + }); + test('returns correct group and index for off end of all groups', () => { + const insertPosition = 10; + const result = getGroupAndIndex( itemGroups, insertPosition ); + expect(result.group).toBe(2); + expect(result.index).toBe(5); + }); + test('returns correct group and index for position 0', () => { + const insertPosition = 0; + const result = getGroupAndIndex([], insertPosition ); + expect(result.group).toBe(0); + expect(result.index).toBe(0); }); }); From a23cc231559bea5c2c5e66723f367aad87bad4f6 Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 15 Jan 2020 11:30:20 +0000 Subject: [PATCH 374/760] Collect x-teaser-timeline model logic into a single module --- .../__tests__/lib/transform.test.js | 100 +++++++++++++----- .../x-teaser-timeline/src/TeaserTimeline.jsx | 17 +-- .../x-teaser-timeline/src/lib/transform.js | 23 +++- 3 files changed, 94 insertions(+), 46 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js index de3ebcd0f..16bff2549 100644 --- a/components/x-teaser-timeline/__tests__/lib/transform.test.js +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -1,4 +1,4 @@ -import { getItemGroups, getGroupAndIndex } from '../../src/lib/transform'; +import { buildModel } from '../../src/lib/transform'; const props = { 'items': [ @@ -55,11 +55,9 @@ const props = { ], 'showSaveButtons': false, 'latestItemsTime': null, - 'customSlotContent': { 'children': [], 'attributes': { 'shouldRender': false } }, - 'customSlotPosition': 2, 'children': [] }; -const itemGroups = [ +const groupedItems = [ { 'date': '2020-01-14', 'title': 'Earlier Today', @@ -151,36 +149,80 @@ const itemGroups = [ } ]; -describe.only('getItemGroups', () => { - test('correctly groups some stuff', () => { - const result = getItemGroups({ ...props, timezoneOffset: 0, localTodayDate: '2020-01-14' }); - expect(result).toEqual(itemGroups); +describe.only('buildModel without custom slot content', () => { + test('correctly builds model from props', () => { + const result = buildModel({ ...props, timezoneOffset: 0, localTodayDate: '2020-01-14' }); + expect(result).toEqual(groupedItems); }); }); -describe.only('getGroupAndIndex', () => { - test('returns correct group and index for middle of first group', () => { - const insertPosition = 2; - const result = getGroupAndIndex( itemGroups, insertPosition ); - expect(result.group).toBe(0); - expect(result.index).toBe(2); +describe.only('buildModel with custom slot content', () => { + test('returns correct model for custom slot in middle of first group', () => { + const result = buildModel({ + ...props, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: { foo: 1 }, + customSlotPosition: 2 + }); + expect(result).toEqual([ + { + ...groupedItems[0], + items: [groupedItems[0].items[0], groupedItems[0].items[1], { foo: 1 }, groupedItems[0].items[2], groupedItems[0].items[3]] + }, + groupedItems[1], + groupedItems[2] + ]); }); - test('returns correct group and index for end of second group', () => { - const insertPosition = 5; - const result = getGroupAndIndex( itemGroups, insertPosition ); - expect(result.group).toBe(1); - expect(result.index).toBe(1); + test('returns correct model for custom slot at end of second group', () => { + const result = buildModel({ + ...props, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: { foo: 1 }, + customSlotPosition: 5 + }); + expect(result).toEqual([ + groupedItems[0], + { + ...groupedItems[1], + items: [groupedItems[1].items[0], { foo: 1 }] + }, + groupedItems[2] + ]); }); - test('returns correct group and index for off end of all groups', () => { - const insertPosition = 10; - const result = getGroupAndIndex( itemGroups, insertPosition ); - expect(result.group).toBe(2); - expect(result.index).toBe(5); + test('returns correct model for custom slot off end of all groups', () => { + const result = buildModel({ + ...props, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: { foo: 1 }, + customSlotPosition: 10 + }); + expect(result).toEqual([ + groupedItems[0], + groupedItems[1], + { + ...groupedItems[2], + items: [...groupedItems[2].items, { foo: 1 }] + } + ]); }); - test('returns correct group and index for position 0', () => { - const insertPosition = 0; - const result = getGroupAndIndex([], insertPosition ); - expect(result.group).toBe(0); - expect(result.index).toBe(0); + test('returns correct model for custom slot in position 0', () => { + const result = buildModel({ + ...props, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: { foo: 1 }, + customSlotPosition: 0 + }); + expect(result).toEqual([ + { + ...groupedItems[0], + items: [{ foo: 1 }, groupedItems[0].items[0], groupedItems[0].items[1], groupedItems[0].items[2], groupedItems[0].items[3]] + }, + groupedItems[1], + groupedItems[2] + ]); }); }); diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index f2ba515a4..711bf2365 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -1,29 +1,16 @@ import { h } from '@financial-times/x-engine'; import { ArticleSaveButton } from '@financial-times/x-article-save-button'; import { Teaser, presets } from '@financial-times/x-teaser'; -import { getGroupAndIndex, getItemGroups } from './lib/transform'; +import { buildModel } from './lib/transform'; import styles from './TeaserTimeline.scss'; import classNames from 'classnames'; const TeaserTimeline = props => { const { csrfToken = null, - customSlotContent, - customSlotPosition = 2, - items, showSaveButtons = true } = props; - const itemGroups = getItemGroups(props); - - if (itemGroups.length > 0 && customSlotContent) { - const insertPosition = Math.min(customSlotPosition, items.length); - const insert = getGroupAndIndex(itemGroups, insertPosition); - const copyOfItems = [...itemGroups[insert.group].items]; - - copyOfItems.splice(insert.index, 0, customSlotContent); - - itemGroups[insert.group].items = copyOfItems; - } + const itemGroups = buildModel(props); return itemGroups.length > 0 && (
    diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index 7115bde20..776173e3c 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -67,7 +67,7 @@ const addItemGroupTitles = (itemGroups, localTodayDate) => { }); }; -export const getItemGroups = props => { +const getItemGroups = props => { const now = new Date(); const { items, @@ -89,7 +89,7 @@ export const getItemGroups = props => { return addItemGroupTitles(itemGroups, localTodayDate); }; -export const getGroupAndIndex = (groups, position) => { +const getGroupAndIndex = (groups, position) => { if (position > 0) { const group = groups.findIndex(g => g.items.some(item => item.articleIndex === position - 1)); const index = groups[group].items.findIndex(item => item.articleIndex === position - 1); @@ -105,3 +105,22 @@ export const getGroupAndIndex = (groups, position) => { index: 0 }; }; + +export const buildModel = props => { + const itemGroups = getItemGroups(props); + const { customSlotContent, + customSlotPosition = 2, + items + } = props; + + if (itemGroups.length > 0 && customSlotContent) { + const insertPosition = Math.min(customSlotPosition, items.length); + const insert = getGroupAndIndex(itemGroups, insertPosition); + const copyOfItems = [...itemGroups[insert.group].items]; + + copyOfItems.splice(insert.index, 0, customSlotContent); + + itemGroups[insert.group].items = copyOfItems; + } + return itemGroups; +}; From a95978a75411a3e94f5ea5bd76e87e5003bf909f Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 15 Jan 2020 12:03:44 +0000 Subject: [PATCH 375/760] Adds test for split today --- .../__tests__/lib/transform.test.js | 273 ++++++++++-------- .../x-teaser-timeline/src/TeaserTimeline.jsx | 12 +- .../x-teaser-timeline/src/lib/transform.js | 18 +- 3 files changed, 161 insertions(+), 142 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js index 16bff2549..3e51fbb6f 100644 --- a/components/x-teaser-timeline/__tests__/lib/transform.test.js +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -1,62 +1,58 @@ import { buildModel } from '../../src/lib/transform'; -const props = { - 'items': [ - { - 'id': '01f0b004-36b9-11ea-a6d3-9a26f8c3cba4', - 'title': 'Europeans step up pressure on Iran over nuclear deal', - 'publishedDate': '2020-01-14T11:10:26.000Z' - }, - { - 'id': '01eaf2fc-36ac-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran’s judiciary threatens to expel UK ambassador', - 'publishedDate': '2020-01-14T10:23:14.000Z' - }, - { - 'id': 'dcac61ea-361c-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran’s regime loses the battle for public opinion', - 'publishedDate': '2020-01-14T10:00:27.000Z' - }, - { - 'id': 'bf0752ee-3685-11ea-a6d3-9a26f8c3cba4', - 'title': 'Justin Trudeau partly blames US for Iran plane crash', - 'publishedDate': '2020-01-14T08:15:05.000Z' - }, - { - 'id': '1d1527fa-356c-11ea-a6d3-9a26f8c3cba4', - 'title': 'Biden and Sanders reprise Iraq war fight in 2020 race', - 'publishedDate': '2020-01-13T11:00:26.000Z' - }, - { - 'id': '6524b530-355b-11ea-a6d3-9a26f8c3cba4', - 'title': 'Esper ‘didn’t see’ specific evidence of embassy threat', - 'publishedDate': '2020-01-12T18:36:39.000Z' - }, - { - 'id': '86df67f6-3524-11ea-a6d3-9a26f8c3cba4', - 'title': 'Lies over air crash shake Iran’s trust in its rulers', - 'publishedDate': '2020-01-12T16:29:11.000Z' - }, - { - 'id': '37084c8c-3508-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran questions Revolutionary Guard over downing of airliner', - 'publishedDate': '2020-01-12T09:51:13.000Z' - }, - { - 'id': 'b931cc22-3073-11ea-a329-0bcf87a328f2', - 'title': 'What next for oil as US-Iran tensions simmer?', - 'publishedDate': '2020-01-12T09:00:26.000Z' - }, - { - 'id': '0e76e39a-3428-11ea-9703-eea0cae3f0de', - 'title': 'Iran admits it shot down Ukrainian jet', - 'publishedDate': '2020-01-12T05:35:29.000Z' - } - ], - 'showSaveButtons': false, - 'latestItemsTime': null, - 'children': [] -}; +const items = [ + { + 'id': '01f0b004-36b9-11ea-a6d3-9a26f8c3cba4', + 'title': 'Europeans step up pressure on Iran over nuclear deal', + 'publishedDate': '2020-01-14T11:10:26.000Z' + }, + { + 'id': '01eaf2fc-36ac-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran’s judiciary threatens to expel UK ambassador', + 'publishedDate': '2020-01-14T10:23:14.000Z' + }, + { + 'id': 'dcac61ea-361c-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran’s regime loses the battle for public opinion', + 'publishedDate': '2020-01-14T10:00:27.000Z' + }, + { + 'id': 'bf0752ee-3685-11ea-a6d3-9a26f8c3cba4', + 'title': 'Justin Trudeau partly blames US for Iran plane crash', + 'publishedDate': '2020-01-14T08:15:05.000Z' + }, + { + 'id': '1d1527fa-356c-11ea-a6d3-9a26f8c3cba4', + 'title': 'Biden and Sanders reprise Iraq war fight in 2020 race', + 'publishedDate': '2020-01-13T11:00:26.000Z' + }, + { + 'id': '6524b530-355b-11ea-a6d3-9a26f8c3cba4', + 'title': 'Esper ‘didn’t see’ specific evidence of embassy threat', + 'publishedDate': '2020-01-12T18:36:39.000Z' + }, + { + 'id': '86df67f6-3524-11ea-a6d3-9a26f8c3cba4', + 'title': 'Lies over air crash shake Iran’s trust in its rulers', + 'publishedDate': '2020-01-12T16:29:11.000Z' + }, + { + 'id': '37084c8c-3508-11ea-a6d3-9a26f8c3cba4', + 'title': 'Iran questions Revolutionary Guard over downing of airliner', + 'publishedDate': '2020-01-12T09:51:13.000Z' + }, + { + 'id': 'b931cc22-3073-11ea-a329-0bcf87a328f2', + 'title': 'What next for oil as US-Iran tensions simmer?', + 'publishedDate': '2020-01-12T09:00:26.000Z' + }, + { + 'id': '0e76e39a-3428-11ea-9703-eea0cae3f0de', + 'title': 'Iran admits it shot down Ukrainian jet', + 'publishedDate': '2020-01-12T05:35:29.000Z' + } +]; + const groupedItems = [ { 'date': '2020-01-14', @@ -149,80 +145,107 @@ const groupedItems = [ } ]; -describe.only('buildModel without custom slot content', () => { - test('correctly builds model from props', () => { - const result = buildModel({ ...props, timezoneOffset: 0, localTodayDate: '2020-01-14' }); - expect(result).toEqual(groupedItems); +describe('buildModel', () => { + describe('without custom slot content', () => { + test('correctly builds model', () => { + const result = buildModel({ items, timezoneOffset: 0, localTodayDate: '2020-01-14' }); + expect(result).toEqual(groupedItems); + }); }); -}); -describe.only('buildModel with custom slot content', () => { - test('returns correct model for custom slot in middle of first group', () => { - const result = buildModel({ - ...props, - timezoneOffset: 0, - localTodayDate: '2020-01-14', - customSlotContent: { foo: 1 }, - customSlotPosition: 2 + describe('with latestItemsTime today', () => { + test('correctly builds model with today\'s group split', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + latestItemsTime: '2020-01-14T10:00:00+00:00' + }); + expect(result).toEqual([ + { + date: 'today-latest', + title: 'Latest News', + items: [groupedItems[0].items[0], groupedItems[0].items[1], groupedItems[0].items[2]] + }, + { + ...groupedItems[0], + date: 'today-earlier', + items: [groupedItems[0].items[3]] + }, + groupedItems[1], + groupedItems[2] + ]); }); - expect(result).toEqual([ - { - ...groupedItems[0], - items: [groupedItems[0].items[0], groupedItems[0].items[1], { foo: 1 }, groupedItems[0].items[2], groupedItems[0].items[3]] - }, - groupedItems[1], - groupedItems[2] - ]); }); - test('returns correct model for custom slot at end of second group', () => { - const result = buildModel({ - ...props, - timezoneOffset: 0, - localTodayDate: '2020-01-14', - customSlotContent: { foo: 1 }, - customSlotPosition: 5 + + describe('with custom slot content', () => { + test('returns correct model for custom slot in middle of first group', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: { foo: 1 }, + customSlotPosition: 2 + }); + expect(result).toEqual([ + { + ...groupedItems[0], + items: [groupedItems[0].items[0], groupedItems[0].items[1], { foo: 1 }, groupedItems[0].items[2], groupedItems[0].items[3]] + }, + groupedItems[1], + groupedItems[2] + ]); }); - expect(result).toEqual([ - groupedItems[0], - { - ...groupedItems[1], - items: [groupedItems[1].items[0], { foo: 1 }] - }, - groupedItems[2] - ]); - }); - test('returns correct model for custom slot off end of all groups', () => { - const result = buildModel({ - ...props, - timezoneOffset: 0, - localTodayDate: '2020-01-14', - customSlotContent: { foo: 1 }, - customSlotPosition: 10 + test('returns correct model for custom slot at end of second group', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: { foo: 1 }, + customSlotPosition: 5 + }); + expect(result).toEqual([ + groupedItems[0], + { + ...groupedItems[1], + items: [groupedItems[1].items[0], { foo: 1 }] + }, + groupedItems[2] + ]); }); - expect(result).toEqual([ - groupedItems[0], - groupedItems[1], - { - ...groupedItems[2], - items: [...groupedItems[2].items, { foo: 1 }] - } - ]); - }); - test('returns correct model for custom slot in position 0', () => { - const result = buildModel({ - ...props, - timezoneOffset: 0, - localTodayDate: '2020-01-14', - customSlotContent: { foo: 1 }, - customSlotPosition: 0 + test('returns correct model for custom slot off end of all groups', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: { foo: 1 }, + customSlotPosition: 10 + }); + expect(result).toEqual([ + groupedItems[0], + groupedItems[1], + { + ...groupedItems[2], + items: [...groupedItems[2].items, { foo: 1 }] + } + ]); + }); + test('returns correct model for custom slot in position 0', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: { foo: 1 }, + customSlotPosition: 0 + }); + expect(result).toEqual([ + { + ...groupedItems[0], + items: [{ foo: 1 }, groupedItems[0].items[0], groupedItems[0].items[1], groupedItems[0].items[2], groupedItems[0].items[3]] + }, + groupedItems[1], + groupedItems[2] + ]); }); - expect(result).toEqual([ - { - ...groupedItems[0], - items: [{ foo: 1 }, groupedItems[0].items[0], groupedItems[0].items[1], groupedItems[0].items[2], groupedItems[0].items[3]] - }, - groupedItems[1], - groupedItems[2] - ]); }); }); diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index 711bf2365..552cf9e2a 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -6,11 +6,19 @@ import styles from './TeaserTimeline.scss'; import classNames from 'classnames'; const TeaserTimeline = props => { + const now = new Date(); const { csrfToken = null, - showSaveButtons = true + showSaveButtons = true, + customSlotContent, + customSlotPosition = 2, + items, + timezoneOffset = now.getTimezoneOffset(), + localTodayDate = getDateOnly(now.toISOString()), + latestItemsTime } = props; - const itemGroups = buildModel(props); + + const itemGroups = buildModel({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime}); return itemGroups.length > 0 && (
    diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index 776173e3c..9e9b91945 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -67,15 +67,7 @@ const addItemGroupTitles = (itemGroups, localTodayDate) => { }); }; -const getItemGroups = props => { - const now = new Date(); - const { - items, - timezoneOffset = now.getTimezoneOffset(), - localTodayDate = getDateOnly(now.toISOString()), - latestItemsTime - } = props; - +const getItemGroups = ({items, timezoneOffset, localTodayDate, latestItemsTime}) => { if (!items || !Array.isArray(items) || items.length === 0) { return []; } @@ -106,12 +98,8 @@ const getGroupAndIndex = (groups, position) => { }; }; -export const buildModel = props => { - const itemGroups = getItemGroups(props); - const { customSlotContent, - customSlotPosition = 2, - items - } = props; +export const buildModel = ({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime}) => { + const itemGroups = getItemGroups({items, timezoneOffset, localTodayDate, latestItemsTime}); if (itemGroups.length > 0 && customSlotContent) { const insertPosition = Math.min(customSlotPosition, items.length); From b4fcedcd3bcee721a7a03877b9bbcea18fdd4e1c Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 15 Jan 2020 12:16:20 +0000 Subject: [PATCH 376/760] Move getDateOnly into module where it can be reused --- components/x-teaser-timeline/src/TeaserTimeline.jsx | 1 + components/x-teaser-timeline/src/lib/date.js | 2 ++ components/x-teaser-timeline/src/lib/transform.js | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index 552cf9e2a..e3ca8f527 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -2,6 +2,7 @@ import { h } from '@financial-times/x-engine'; import { ArticleSaveButton } from '@financial-times/x-article-save-button'; import { Teaser, presets } from '@financial-times/x-teaser'; import { buildModel } from './lib/transform'; +import { getDateOnly } from './lib/date'; import styles from './TeaserTimeline.scss'; import classNames from 'classnames'; diff --git a/components/x-teaser-timeline/src/lib/date.js b/components/x-teaser-timeline/src/lib/date.js index 53873a08a..2ce0f5859 100644 --- a/components/x-teaser-timeline/src/lib/date.js +++ b/components/x-teaser-timeline/src/lib/date.js @@ -47,3 +47,5 @@ export const splitLatestEarlier = (items, splitDate) => { return { latestItems, earlierItems }; }; + +export const getDateOnly = date => date.substr(0, 10); diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index 9e9b91945..a14321860 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -1,10 +1,10 @@ import { getLocalisedISODate, getTitleForItemGroup, - splitLatestEarlier + splitLatestEarlier, + getDateOnly } from './date'; -const getDateOnly = date => date.substr(0, 10); const groupItemsByLocalisedDate = (items, timezoneOffset) => { const itemsByLocalisedDate = {}; From 04d0fcb3e7685b7d4e6b6ddd0f321e3854e25deb Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 22 Jan 2020 08:57:26 +0000 Subject: [PATCH 377/760] Add link to x-teaser documentation --- components/x-teaser-timeline/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md index c0730f64e..05c21e204 100644 --- a/components/x-teaser-timeline/readme.md +++ b/components/x-teaser-timeline/readme.md @@ -27,6 +27,8 @@ $o-teaser-is-silent: true; @include oTeaser(('default', 'images', 'timestamp'), ('small')); ``` +See the [x-teaser](https://github.com/Financial-Times/x-dash/tree/master/components/x-teaser) documentation. + ## Usage The components provided by this module are all functions that expect a map of [properties](#properties). They can be used with vanilla JavaScript or JSX (If you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: From 91a4104d6d900af1e94b45beeef803eb2e8ed3ef Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 22 Jan 2020 09:05:53 +0000 Subject: [PATCH 378/760] Add comment to warn of duplication in CSS output --- components/x-teaser-timeline/src/TeaserTimeline.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-teaser-timeline/src/TeaserTimeline.scss b/components/x-teaser-timeline/src/TeaserTimeline.scss index 14f66575a..432b276f8 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.scss +++ b/components/x-teaser-timeline/src/TeaserTimeline.scss @@ -2,6 +2,7 @@ @import 'o-grid/main'; @import 'o-typography/main'; +/* There is a risk that this chunk will get duplicated if you include the save button component directly*/ :global { @import "~@financial-times/x-article-save-button/dist/ArticleSaveButton"; } From a17569c4e258a3725c43662c409a83292a9fc0b4 Mon Sep 17 00:00:00 2001 From: Edd Sowden Date: Wed, 22 Jan 2020 09:56:08 +0000 Subject: [PATCH 379/760] Update snapshots --- .../__snapshots__/snapshots.test.js.snap | 46 ++-- .../TeaserTimeline.test.jsx.snap | 230 +++++++++--------- 2 files changed, 138 insertions(+), 138 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index 3e66a50e5..e944b16cc 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -794,7 +794,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -892,7 +892,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1003,7 +1003,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1108,7 +1108,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1211,7 +1211,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1309,7 +1309,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1420,7 +1420,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1523,7 +1523,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1621,7 +1621,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1719,7 +1719,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1805,7 +1805,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1891,7 +1891,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -1989,7 +1989,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2160,7 +2160,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2258,7 +2258,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2369,7 +2369,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2516,7 +2516,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2619,7 +2619,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2717,7 +2717,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2815,7 +2815,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2913,7 +2913,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -2999,7 +2999,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    @@ -3097,7 +3097,7 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    diff --git a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap index 4115cc63c..4ed1ec542 100644 --- a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap +++ b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap @@ -79,7 +79,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -177,7 +177,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -275,7 +275,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -373,7 +373,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -476,7 +476,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -574,7 +574,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -685,7 +685,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -788,7 +788,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -886,7 +886,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -984,7 +984,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1070,7 +1070,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1156,7 +1156,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1254,7 +1254,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1425,7 +1425,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1523,7 +1523,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1634,7 +1634,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1781,7 +1781,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1884,7 +1884,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -1982,7 +1982,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -2080,7 +2080,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -2178,7 +2178,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -2264,7 +2264,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -2362,7 +2362,7 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes
    @@ -2605,7 +2605,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -2703,7 +2703,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -2801,7 +2801,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -2899,7 +2899,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3002,7 +3002,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3100,7 +3100,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3211,7 +3211,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3314,7 +3314,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3412,7 +3412,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3510,7 +3510,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3596,7 +3596,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3682,7 +3682,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3780,7 +3780,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -3951,7 +3951,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4049,7 +4049,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4160,7 +4160,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4307,7 +4307,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4410,7 +4410,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4508,7 +4508,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4606,7 +4606,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4704,7 +4704,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4790,7 +4790,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -4888,7 +4888,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is not same date as
    @@ -5131,7 +5131,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -5229,7 +5229,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -5340,7 +5340,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -5438,7 +5438,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -5541,7 +5541,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -5639,7 +5639,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -5750,7 +5750,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -5853,7 +5853,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -5951,7 +5951,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6049,7 +6049,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6135,7 +6135,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6221,7 +6221,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6319,7 +6319,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6490,7 +6490,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6588,7 +6588,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6699,7 +6699,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6846,7 +6846,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -6949,7 +6949,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -7047,7 +7047,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -7145,7 +7145,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -7243,7 +7243,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -7329,7 +7329,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -7427,7 +7427,7 @@ exports[`x-teaser-timeline given latestItemsTime is set renders latest, earlier,
    @@ -7670,7 +7670,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -7768,7 +7768,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -7866,7 +7866,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -7964,7 +7964,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8067,7 +8067,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8165,7 +8165,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8276,7 +8276,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8379,7 +8379,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8477,7 +8477,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8575,7 +8575,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8661,7 +8661,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8747,7 +8747,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -8845,7 +8845,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9016,7 +9016,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9114,7 +9114,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9225,7 +9225,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9372,7 +9372,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9475,7 +9475,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9573,7 +9573,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9671,7 +9671,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9769,7 +9769,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9855,7 +9855,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -9953,7 +9953,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is not set or i
    @@ -10196,7 +10196,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10269,7 +10269,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10342,7 +10342,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10415,7 +10415,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10493,7 +10493,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10566,7 +10566,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10652,7 +10652,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10730,7 +10730,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10803,7 +10803,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10876,7 +10876,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10937,7 +10937,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -10998,7 +10998,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11071,7 +11071,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11192,7 +11192,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11265,7 +11265,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11351,7 +11351,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11448,7 +11448,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11526,7 +11526,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11599,7 +11599,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11672,7 +11672,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11745,7 +11745,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11806,7 +11806,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false @@ -11879,7 +11879,7 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false From 2b27572083c44775875fa45e1d292fd65e0742e9 Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 15 Jan 2020 16:50:49 +0000 Subject: [PATCH 380/760] Allow Latest News to include articles up to 36h before today. --- .../__tests__/TeaserTimeline.test.jsx | 18 +- .../TeaserTimeline.test.jsx.snap | 2528 ++++++++++++++++- .../__tests__/lib/transform.test.js | 38 + .../x-teaser-timeline/src/lib/transform.js | 71 +- 4 files changed, 2614 insertions(+), 41 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx index b13db1fe7..8159f520c 100644 --- a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -48,6 +48,20 @@ describe('x-teaser-timeline', () => { }); }); + describe('given latestItemsTime is set and results in all today\'s and some of yesterday\'s articles being "latest"', () => { + beforeEach(() => { + tree = renderer.create().toJSON(); + }); + + it('renders latest, yesterday and October 15th item groups (no earlier today)', () => { + expect(tree).toMatchSnapshot(); + }); + }); + describe('given latestItemsTime is not set', () => { beforeEach(() => { tree = renderer.create().toJSON(); @@ -58,11 +72,11 @@ describe('x-teaser-timeline', () => { }); }); - describe('given latestItemsTime is set but is not same date as localTodayDate', () => { + describe('given latestItemsTime is set but is more than 36h ago', () => { beforeEach(() => { tree = renderer.create().toJSON(); }); diff --git a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap index 4115cc63c..ff66dddb1 100644 --- a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap +++ b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap @@ -2526,7 +2526,2533 @@ exports[`x-teaser-timeline given latestItemsTime is not set renders earlier, yes `; -exports[`x-teaser-timeline given latestItemsTime is set but is not same date as localTodayDate ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest) 1`] = ` +exports[`x-teaser-timeline given latestItemsTime is set and results in all today's and some of yesterday's articles being "latest" renders latest, yesterday and October 15th item groups (no earlier today) 1`] = ` +
    +
    +

    + Latest News +

    + +
    +
    +

    + Yesterday +

    + +
    +
    +

    + October 15, 2018 +

    + +
    +
    +`; + +exports[`x-teaser-timeline given latestItemsTime is set but is more than 36h ago ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest) 1`] = `
    { }); }); + describe('with latestItemsTime less than 36h ago', () => { + test('correctly builds model with latest items split out', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + latestItemsTime: '2020-01-13T10:00:00+00:00' + }); + expect(result.length).toBe(2); // latest news, 2020-01-12 + expect(result[0].date).toBe('today-latest'); + expect(result[1].date).toBe('2020-01-12'); + + expect(result[0].title).toBe('Latest News'); + expect(result[1].title).toBe('January 12, 2020'); + + expect(result[0].items).toEqual([ + groupedItems[0].items[0], + groupedItems[0].items[1], + groupedItems[0].items[2], + groupedItems[0].items[3], + groupedItems[1].items[0] + ]); + expect(result[1]).toEqual(groupedItems[2]); + }); + }); + + describe('with latestItemsTime over 36h ago', () => { + test('builds model without latest items', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + latestItemsTime: '2020-01-12T11:59:59+00:00' + }); + expect(result).toEqual(groupedItems); + }); + }); + describe('with custom slot content', () => { test('returns correct model for custom slot in middle of first group', () => { const result = buildModel({ diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index a14321860..e9b057a55 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -1,12 +1,12 @@ import { getLocalisedISODate, getTitleForItemGroup, - splitLatestEarlier, getDateOnly } from './date'; +const LATEST_ARTICLES_CUT_OFF_PERIOD = 36 * 60 * 60 * 1000; // Don't show latest articles if user visited over 36h ago -const groupItemsByLocalisedDate = (items, timezoneOffset) => { +const groupItemsByLocalisedDate = (indexOffset, replaceLocalDate, localTodayDateTime, items, timezoneOffset) => { const itemsByLocalisedDate = {}; items.forEach((item, index) => { @@ -18,45 +18,30 @@ const groupItemsByLocalisedDate = (items, timezoneOffset) => { } item.localisedLastUpdated = localDateTime; - itemsByLocalisedDate[localDate].push({ articleIndex: index, ...item }); + itemsByLocalisedDate[localDate].push({ articleIndex: indexOffset + index, ...item }); }); + const localTodayDate = localTodayDateTime && getDateOnly(localTodayDateTime); return Object.entries(itemsByLocalisedDate).map(([localDate, items]) => ({ - date: localDate, + date: replaceLocalDate && localDate === localTodayDate ? 'today-earlier' : localDate, items })); }; -const splitTodaysItems = (itemGroups, localTodayDate, latestItemsTime) => { - const firstGroupIsToday = itemGroups[0] && itemGroups[0].date === localTodayDate; - const latestTimeIsToday = getDateOnly(latestItemsTime) === localTodayDate; +const splitLatestItems = (items, localTodayDate, latestItemsTime) => { + const latestNews = []; + const remainingItems = []; - if (!firstGroupIsToday || !latestTimeIsToday) { - return itemGroups; - } - - const { latestItems, earlierItems } = splitLatestEarlier(itemGroups[0].items, latestItemsTime); - const splitGroups = []; - - if (latestItems.length) { - splitGroups.push({ - date: 'today-latest', - items: latestItems - }); - } - - if (earlierItems.length) { - splitGroups.push({ - date: 'today-earlier', - items: earlierItems - }); - } - - if (splitGroups.length) { - itemGroups.splice(0, 1, ...splitGroups); - } + items.forEach( (item,index) => { + // These are ISO date strings so string comparison works when comparing them if they are in the same timezone + if( latestItemsTime && item.publishedDate > latestItemsTime ) { + latestNews.push({ articleIndex: index, ...item }); + } else { + remainingItems.push(item); + } + }); - return itemGroups; + return [latestNews,remainingItems]; }; const addItemGroupTitles = (itemGroups, localTodayDate) => { @@ -67,15 +52,24 @@ const addItemGroupTitles = (itemGroups, localTodayDate) => { }); }; -const getItemGroups = ({items, timezoneOffset, localTodayDate, latestItemsTime}) => { - if (!items || !Array.isArray(items) || items.length === 0) { +const getItemGroups = ({sortedItems, timezoneOffset, localTodayDate, latestItemsTime}) => { + if (!sortedItems || !Array.isArray(sortedItems) || sortedItems.length === 0) { return []; } - let itemGroups = groupItemsByLocalisedDate(items, timezoneOffset); + const isLatestItemTimeWithinRange = latestItemsTime && (new Date(localTodayDate)-new Date(latestItemsTime)) < LATEST_ARTICLES_CUT_OFF_PERIOD; + const [latestItems,remainingItems] = isLatestItemTimeWithinRange ? splitLatestItems(sortedItems, localTodayDate, latestItemsTime) : [[],sortedItems]; + + let itemGroups = groupItemsByLocalisedDate(latestItems.length, isLatestItemTimeWithinRange, localTodayDate, remainingItems, timezoneOffset); - if (latestItemsTime) { - itemGroups = splitTodaysItems(itemGroups, localTodayDate, latestItemsTime); + if (latestItems.length > 0) { + itemGroups = [ + { + date: 'today-latest', + items: latestItems + }, + ...itemGroups + ]; } return addItemGroupTitles(itemGroups, localTodayDate); @@ -99,7 +93,8 @@ const getGroupAndIndex = (groups, position) => { }; export const buildModel = ({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime}) => { - const itemGroups = getItemGroups({items, timezoneOffset, localTodayDate, latestItemsTime}); + const sortedItems = items ? [...items].sort( (a,b) => a.publishedDate > b.publishedDate ? -1 : 1 ) : []; + const itemGroups = getItemGroups({sortedItems, timezoneOffset, localTodayDate, latestItemsTime}); if (itemGroups.length > 0 && customSlotContent) { const insertPosition = Math.min(customSlotPosition, items.length); From 6908fba70796cfa586677e03a225786a8eb5e4c0 Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Tue, 21 Jan 2020 09:44:56 +0000 Subject: [PATCH 381/760] Default to previous behaviour unless latestItemsAge is specified --- .../__tests__/TeaserTimeline.test.jsx | 17 +- .../TeaserTimeline.test.jsx.snap | 2528 ++++++++++++++++- .../__tests__/lib/transform.test.js | 10 +- .../x-teaser-timeline/src/TeaserTimeline.jsx | 5 +- .../x-teaser-timeline/src/lib/transform.js | 13 +- 5 files changed, 2559 insertions(+), 14 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx index 8159f520c..a0d163e26 100644 --- a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -54,6 +54,7 @@ describe('x-teaser-timeline', () => { {...props} timezoneOffset={0} latestItemsTime='2018-10-16T11:59:59.999Z' + latestItemsAgeHours={36} />).toJSON(); }); @@ -72,11 +73,25 @@ describe('x-teaser-timeline', () => { }); }); - describe('given latestItemsTime is set but is more than 36h ago', () => { + describe('given latestItemsTime is set but is more than latestItemsAgeHours ago', () => { beforeEach(() => { tree = renderer.create().toJSON(); + }); + + it('ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest)', () => { + expect(tree).toMatchSnapshot(); + }); + }); + + describe('given latestItemsTime is set but is not same date as localTodayDate', () => { + beforeEach(() => { + tree = renderer.create().toJSON(); }); diff --git a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap index ff66dddb1..ccf2ef4a9 100644 --- a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap +++ b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap @@ -5052,7 +5052,2533 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today
    `; -exports[`x-teaser-timeline given latestItemsTime is set but is more than 36h ago ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest) 1`] = ` +exports[`x-teaser-timeline given latestItemsTime is set but is more than latestItemsAgeHours ago ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest) 1`] = ` +
    +
    +

    + Earlier Today +

    + +
    +
    +

    + Yesterday +

    + +
    +
    +

    + October 15, 2018 +

    + +
    +
    +`; + +exports[`x-teaser-timeline given latestItemsTime is set but is not same date as localTodayDate ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest) 1`] = `
    { }); }); - describe('with latestItemsTime less than 36h ago', () => { + describe('with latestItemsTime less than latestItemsAgeHours ago', () => { test('correctly builds model with latest items split out', () => { const result = buildModel({ items, timezoneOffset: 0, localTodayDate: '2020-01-14', - latestItemsTime: '2020-01-13T10:00:00+00:00' + latestItemsTime: '2020-01-13T10:00:00+00:00', + latestItemsAgeHours: 36 }); expect(result.length).toBe(2); // latest news, 2020-01-12 expect(result[0].date).toBe('today-latest'); @@ -204,13 +205,14 @@ describe('buildModel', () => { }); }); - describe('with latestItemsTime over 36h ago', () => { + describe('with latestItemsTime over latestItemsAgeHours ago', () => { test('builds model without latest items', () => { const result = buildModel({ items, timezoneOffset: 0, localTodayDate: '2020-01-14', - latestItemsTime: '2020-01-12T11:59:59+00:00' + latestItemsTime: '2020-01-12T11:59:59+00:00', + latestItemsAgeHours: 36 }); expect(result).toEqual(groupedItems); }); diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index e3ca8f527..76282c9e8 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -16,10 +16,11 @@ const TeaserTimeline = props => { items, timezoneOffset = now.getTimezoneOffset(), localTodayDate = getDateOnly(now.toISOString()), - latestItemsTime + latestItemsTime, + latestItemsAgeHours } = props; - const itemGroups = buildModel({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime}); + const itemGroups = buildModel({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}); return itemGroups.length > 0 && (
    diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index e9b057a55..185be27d2 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -4,8 +4,6 @@ import { getDateOnly } from './date'; -const LATEST_ARTICLES_CUT_OFF_PERIOD = 36 * 60 * 60 * 1000; // Don't show latest articles if user visited over 36h ago - const groupItemsByLocalisedDate = (indexOffset, replaceLocalDate, localTodayDateTime, items, timezoneOffset) => { const itemsByLocalisedDate = {}; @@ -52,12 +50,15 @@ const addItemGroupTitles = (itemGroups, localTodayDate) => { }); }; -const getItemGroups = ({sortedItems, timezoneOffset, localTodayDate, latestItemsTime}) => { +const getItemGroups = ({sortedItems, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}) => { if (!sortedItems || !Array.isArray(sortedItems) || sortedItems.length === 0) { return []; } - const isLatestItemTimeWithinRange = latestItemsTime && (new Date(localTodayDate)-new Date(latestItemsTime)) < LATEST_ARTICLES_CUT_OFF_PERIOD; + const isLatestItemTimeWithinRange = latestItemsAgeHours ? + latestItemsTime && (new Date(localTodayDate)-new Date(latestItemsTime)) < (latestItemsAgeHours * 60 * 60 * 1000) : + latestItemsTime && (getDateOnly(localTodayDate) === getDateOnly(latestItemsTime)); + const [latestItems,remainingItems] = isLatestItemTimeWithinRange ? splitLatestItems(sortedItems, localTodayDate, latestItemsTime) : [[],sortedItems]; let itemGroups = groupItemsByLocalisedDate(latestItems.length, isLatestItemTimeWithinRange, localTodayDate, remainingItems, timezoneOffset); @@ -92,9 +93,9 @@ const getGroupAndIndex = (groups, position) => { }; }; -export const buildModel = ({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime}) => { +export const buildModel = ({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}) => { const sortedItems = items ? [...items].sort( (a,b) => a.publishedDate > b.publishedDate ? -1 : 1 ) : []; - const itemGroups = getItemGroups({sortedItems, timezoneOffset, localTodayDate, latestItemsTime}); + const itemGroups = getItemGroups({sortedItems, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}); if (itemGroups.length > 0 && customSlotContent) { const insertPosition = Math.min(customSlotPosition, items.length); From b37a3d7b7cf19b93e52bbd22987b5f3a947de35f Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Mon, 27 Jan 2020 09:20:40 +0000 Subject: [PATCH 382/760] Update readme --- components/x-teaser-timeline/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md index c0730f64e..9dbef46dd 100644 --- a/components/x-teaser-timeline/readme.md +++ b/components/x-teaser-timeline/readme.md @@ -57,6 +57,7 @@ Feature | Type | Notes `customSlotContent` | String | Content to insert at `customSlotPosition`. `customSlotPosition` | Number | (Default is 2). Where to insert `customSlotContent`. The custom content will be inserted after the item at this position number. If this position is greater than the number items to render, then it will be inserted last. `csrfToken` | String | A CSRF token that will be used by the save buttons (if shown). +`latestItemsAgeHours`| Number | (Optional). If provided, used to calculate a cutoff time before which no article will count as "latest", regardless of the value of `latestItemsTime`. If omitted, articles before midnight this morning will not count as "latest". Example: From f99819740865d318923bd0d1948cfaa4810a5573 Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Wed, 29 Jan 2020 09:51:13 +0000 Subject: [PATCH 383/760] Clarify getItemGroups a bit --- .../x-teaser-timeline/src/lib/transform.js | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index 185be27d2..cf5954191 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -50,18 +50,41 @@ const addItemGroupTitles = (itemGroups, localTodayDate) => { }); }; -const getItemGroups = ({sortedItems, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}) => { - if (!sortedItems || !Array.isArray(sortedItems) || sortedItems.length === 0) { +const isTimeWithinAgeRange = (time, localTodayDate, ageRangeHours) => + time && (new Date(localTodayDate) - new Date(time)) < (ageRangeHours * 60 * 60 * 1000); + +const isTimeWithinToday = (time, localTodayDate) => + time && (getDateOnly(localTodayDate) === getDateOnly(time)); + +/** + * Groups items (articles) by date + * + * Takes an array of article items and groups them into sections by date. + * Gives the groups presentable titles, e.g. "Earlier Today", and "Yesterday". + * Will try to create a "Latest News" section if `latestItemsTime` is specified and there are articles within the + * permitted age range. This range can be set using `latestItemsAgeRange`. + * + * @param {Item[]} items An array of news articles. + * @param {number} timezoneOffset Minutes ahead (negative) or behind UTC + * @param {string} localTodayDate Today's date in client timezone. ISO Date string format. + * @param {string} latestItemsTime Cutoff time for items to be treated as "Latest News". ISO Date string format. + * @param {number} latestItemsAgeRange Maximum age allowed for items in "Latest News". Hours. + * @returns An array of group objects, each containing the group's title, date and items. + */ +const getItemGroups = ({items, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours: latestItemsAgeRange}) => { + if (!items || !Array.isArray(items) || items.length === 0) { return []; } - const isLatestItemTimeWithinRange = latestItemsAgeHours ? - latestItemsTime && (new Date(localTodayDate)-new Date(latestItemsTime)) < (latestItemsAgeHours * 60 * 60 * 1000) : - latestItemsTime && (getDateOnly(localTodayDate) === getDateOnly(latestItemsTime)); + const sortedItems = items ? [...items].sort( (a,b) => a.publishedDate > b.publishedDate ? -1 : 1 ) : []; + + const includeLatestItemsSection = latestItemsAgeRange ? + isTimeWithinAgeRange(latestItemsTime, localTodayDate, latestItemsAgeRange) : + isTimeWithinToday(latestItemsTime, localTodayDate); - const [latestItems,remainingItems] = isLatestItemTimeWithinRange ? splitLatestItems(sortedItems, localTodayDate, latestItemsTime) : [[],sortedItems]; + const [latestItems,remainingItems] = includeLatestItemsSection ? splitLatestItems(sortedItems, localTodayDate, latestItemsTime) : [[],sortedItems]; - let itemGroups = groupItemsByLocalisedDate(latestItems.length, isLatestItemTimeWithinRange, localTodayDate, remainingItems, timezoneOffset); + let itemGroups = groupItemsByLocalisedDate(latestItems.length, includeLatestItemsSection, localTodayDate, remainingItems, timezoneOffset); if (latestItems.length > 0) { itemGroups = [ @@ -94,8 +117,7 @@ const getGroupAndIndex = (groups, position) => { }; export const buildModel = ({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}) => { - const sortedItems = items ? [...items].sort( (a,b) => a.publishedDate > b.publishedDate ? -1 : 1 ) : []; - const itemGroups = getItemGroups({sortedItems, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}); + const itemGroups = getItemGroups({items, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}); if (itemGroups.length > 0 && customSlotContent) { const insertPosition = Math.min(customSlotPosition, items.length); From db7a9b93c0a8f518b59606c961f9242f54938e4c Mon Sep 17 00:00:00 2001 From: Alex Curtis Date: Fri, 31 Jan 2020 13:26:09 +0000 Subject: [PATCH 384/760] Extract function and update comments --- .../x-teaser-timeline/src/lib/transform.js | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index cf5954191..64ddbaed7 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -56,13 +56,29 @@ const isTimeWithinAgeRange = (time, localTodayDate, ageRangeHours) => const isTimeWithinToday = (time, localTodayDate) => time && (getDateOnly(localTodayDate) === getDateOnly(time)); +/** + * Determines whether a "Latest News" section can be shown in the timeline. + * + * A "Latest News" section is allowed if `latestItemsTime` is specified and lies within the + * permitted time range. This time age range defaults to "today", but can be overridden using + * `latestItemsAgeRange`. + * + * @param {string} localTodayDate Today's date in client timezone. ISO Date string format. + * @param {string} latestItemsTime Cutoff time for items to be treated as "Latest News". ISO Date string format. + * @param {number} latestItemsAgeRange Maximum age allowed for items in "Latest News". Hours. + * @returns {boolean} true if a "Latest News" section can be shown. + */ +const isLatestNewsSectionAllowed = (localTodayDate, latestItemsTime, latestItemsAgeRange) => + latestItemsAgeRange ? + isTimeWithinAgeRange(latestItemsTime, localTodayDate, latestItemsAgeRange) : + isTimeWithinToday(latestItemsTime, localTodayDate); + /** * Groups items (articles) by date * * Takes an array of article items and groups them into sections by date. * Gives the groups presentable titles, e.g. "Earlier Today", and "Yesterday". - * Will try to create a "Latest News" section if `latestItemsTime` is specified and there are articles within the - * permitted age range. This range can be set using `latestItemsAgeRange`. + * Will include a "Latest News" group if allowed by `latestItemsTime` and `latestItemsAgeRange`. * * @param {Item[]} items An array of news articles. * @param {number} timezoneOffset Minutes ahead (negative) or behind UTC @@ -76,15 +92,13 @@ const getItemGroups = ({items, timezoneOffset, localTodayDate, latestItemsTime, return []; } - const sortedItems = items ? [...items].sort( (a,b) => a.publishedDate > b.publishedDate ? -1 : 1 ) : []; + const sortedItems = [...items].sort( (a,b) => a.publishedDate > b.publishedDate ? -1 : 1 ); - const includeLatestItemsSection = latestItemsAgeRange ? - isTimeWithinAgeRange(latestItemsTime, localTodayDate, latestItemsAgeRange) : - isTimeWithinToday(latestItemsTime, localTodayDate); + const includeLatesNewsSection = isLatestNewsSectionAllowed(localTodayDate, latestItemsTime, latestItemsAgeRange); - const [latestItems,remainingItems] = includeLatestItemsSection ? splitLatestItems(sortedItems, localTodayDate, latestItemsTime) : [[],sortedItems]; + const [latestItems,remainingItems] = includeLatesNewsSection ? splitLatestItems(sortedItems, localTodayDate, latestItemsTime) : [[],sortedItems]; - let itemGroups = groupItemsByLocalisedDate(latestItems.length, includeLatestItemsSection, localTodayDate, remainingItems, timezoneOffset); + let itemGroups = groupItemsByLocalisedDate(latestItems.length, includeLatesNewsSection, localTodayDate, remainingItems, timezoneOffset); if (latestItems.length > 0) { itemGroups = [ From ccc9c71e2a3103f7aba5924057800d5532806f94 Mon Sep 17 00:00:00 2001 From: Rob Squires Date: Fri, 7 Feb 2020 12:08:37 +0000 Subject: [PATCH 385/760] update snapshots for x-teaser-timeline - image url seems to have changed, nothing to do with the PR --- .../TeaserTimeline.test.jsx.snap | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap index 2ba0d0df5..f4e0ae7ce 100644 --- a/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap +++ b/components/x-teaser-timeline/__tests__/__snapshots__/TeaserTimeline.test.jsx.snap @@ -2605,7 +2605,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today
    @@ -2703,7 +2703,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today
    @@ -2801,7 +2801,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -2899,7 +2899,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3002,7 +3002,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3100,7 +3100,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3198,7 +3198,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3301,7 +3301,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3399,7 +3399,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3497,7 +3497,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3583,7 +3583,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3669,7 +3669,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3767,7 +3767,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -3951,7 +3951,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4062,7 +4062,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4160,7 +4160,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4307,7 +4307,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4410,7 +4410,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4508,7 +4508,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4606,7 +4606,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4704,7 +4704,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4790,7 +4790,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -4888,7 +4888,7 @@ exports[`x-teaser-timeline given latestItemsTime is set and results in all today @@ -5131,7 +5131,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -5229,7 +5229,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -5327,7 +5327,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -5425,7 +5425,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -5528,7 +5528,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -5626,7 +5626,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -5737,7 +5737,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -5840,7 +5840,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -5938,7 +5938,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6036,7 +6036,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6122,7 +6122,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6208,7 +6208,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6306,7 +6306,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6477,7 +6477,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6575,7 +6575,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6686,7 +6686,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6833,7 +6833,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -6936,7 +6936,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -7034,7 +7034,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -7132,7 +7132,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -7230,7 +7230,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -7316,7 +7316,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI @@ -7414,7 +7414,7 @@ exports[`x-teaser-timeline given latestItemsTime is set but is more than latestI From 7c29e40d3c513da9fe4267cb0bd50687ca76919b Mon Sep 17 00:00:00 2001 From: Rob Squires Date: Fri, 21 Feb 2020 16:31:32 +0000 Subject: [PATCH 386/760] Specify components owned by content disco --- CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 9a2401706..b3f556457 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,8 @@ # See https://help.github.com/articles/about-codeowners/ for more information about this file. * matt.hinchliffe@ft.com bren.brightwell@ft.com + +# component ownership + +components/x-teaser @Financial-Times/content-discovery +components/x-teaser-timeline @Financial-Times/content-discovery From 6a162544cb3b623828b4b9a9a4b682ab028b646e Mon Sep 17 00:00:00 2001 From: chivchila Date: Tue, 25 Feb 2020 12:37:20 +0000 Subject: [PATCH 387/760] fixed article click issue when image is unavailable --- components/x-teaser/src/Image.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 5c81b4ced..99075207e 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -34,15 +34,15 @@ export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) return image ? (
    -
    - +
    - -
    +
    +
    ) : null; }; From 11defb4be428a3fdf9def811597f9633daa7df9f Mon Sep 17 00:00:00 2001 From: chivchila Date: Tue, 25 Feb 2020 14:33:15 +0000 Subject: [PATCH 388/760] updating snapshot tests --- .../__snapshots__/snapshots.test.js.snap | 644 +-- .../TeaserTimeline.test.jsx.snap | 4508 ++++++++--------- .../__snapshots__/snapshots.test.js.snap | 1288 ++--- 3 files changed, 3220 insertions(+), 3220 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index e944b16cc..21021040f 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -777,27 +777,27 @@ exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    @@ -15304,27 +15304,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15377,27 +15377,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15450,27 +15450,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15528,27 +15528,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15601,27 +15601,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15687,27 +15687,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15765,27 +15765,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15838,27 +15838,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15911,27 +15911,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -15972,27 +15972,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16033,27 +16033,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16106,27 +16106,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16227,27 +16227,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16300,27 +16300,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16386,27 +16386,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16483,27 +16483,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16561,27 +16561,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16634,27 +16634,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16707,27 +16707,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16780,27 +16780,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16841,27 +16841,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    @@ -16914,27 +16914,27 @@ exports[`x-teaser-timeline showSaveButtons given showSaveButtons is set to false
    - +
    diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index f35854e50..fdf383ce5 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -35,27 +35,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with article data 1`] = `
    - +
    + `; @@ -95,27 +95,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with article-with-data-image
    - +
    + `; @@ -155,27 +155,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`]
    - +
    + `; @@ -215,27 +215,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = `
    - +
    + `; @@ -280,27 +280,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] =
    - +
    + `; @@ -346,27 +346,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with podcast data 1`] = `
    - +
    + `; @@ -408,27 +408,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = `
    - +
    + `; @@ -468,27 +468,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with topStory data 1`] = `
    - +
    + `; @@ -534,27 +534,27 @@ exports[`x-teaser / snapshots renders a Hero teaser with video data 1`] = `
    - +
    + `; @@ -1036,27 +1036,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article data 1`]
    - +
    + `; @@ -1096,27 +1096,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article-with-dat
    - +
    + `; @@ -1156,27 +1156,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d
    - +
    + `; @@ -1216,27 +1216,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`]
    - +
    + `; @@ -1281,27 +1281,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with packageItem data
    - +
    + `; @@ -1347,27 +1347,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with podcast data 1`]
    - +
    + `; @@ -1409,27 +1409,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1`
    - +
    + `; @@ -1469,27 +1469,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with topStory data 1`
    - +
    + `; @@ -1535,27 +1535,27 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with video data 1`] =
    - +
    + `; @@ -1910,27 +1910,27 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with video data 1`] = `
    - +
    + @@ -1997,27 +1997,27 @@ exports[`x-teaser / snapshots renders a Large teaser with article data 1`] = `
    - +
    + `; @@ -2069,27 +2069,27 @@ exports[`x-teaser / snapshots renders a Large teaser with article-with-data-imag
    - +
    + `; @@ -2141,27 +2141,27 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1`
    - +
    + `; @@ -2213,27 +2213,27 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = `
    - +
    + `; @@ -2290,27 +2290,27 @@ exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] =
    - +
    + `; @@ -2368,27 +2368,27 @@ exports[`x-teaser / snapshots renders a Large teaser with podcast data 1`] = `
    - +
    + `; @@ -2442,27 +2442,27 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = `
    - +
    + `; @@ -2514,27 +2514,27 @@ exports[`x-teaser / snapshots renders a Large teaser with topStory data 1`] = `
    - +
    + `; @@ -2592,27 +2592,27 @@ exports[`x-teaser / snapshots renders a Large teaser with video data 1`] = `
    - +
    + `; @@ -2998,27 +2998,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article data 1`]
    - +
    + `; @@ -3070,27 +3070,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article-with-data
    - +
    + `; @@ -3142,27 +3142,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da
    - +
    + `; @@ -3214,27 +3214,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`]
    - +
    + `; @@ -3291,27 +3291,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data
    - +
    + `; @@ -3369,27 +3369,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with podcast data 1`]
    - +
    + `; @@ -3443,27 +3443,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`]
    - +
    + `; @@ -3515,27 +3515,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with topStory data 1`]
    - +
    + `; @@ -3593,27 +3593,27 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with video data 1`] =
    - +
    + `; @@ -4144,27 +4144,27 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article da
    - +
    + `; @@ -4216,27 +4216,27 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article-wi
    - +
    + `; @@ -4288,27 +4288,27 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac
    - +
    + `; @@ -4360,27 +4360,27 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da
    - +
    + `; @@ -4437,27 +4437,27 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with packageIte
    - +
    + `; @@ -4515,27 +4515,27 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with podcast da
    - +
    + `; @@ -4589,27 +4589,27 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d
    - +
    + `; @@ -4661,27 +4661,27 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with topStory d
    - +
    +
      - + + `; From d04f8a746fcd5268545586bf03d86b6a101d8b25 Mon Sep 17 00:00:00 2001 From: andygout Date: Wed, 6 May 2020 13:31:44 +0100 Subject: [PATCH 389/760] Update CODEOWNERS to @Financial-Times/platforms team --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index b3f556457..269897bcb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,6 +1,6 @@ # See https://help.github.com/articles/about-codeowners/ for more information about this file. -* matt.hinchliffe@ft.com bren.brightwell@ft.com +* @financial-times/platforms # component ownership From 54c0ed4523c493ea9034d4b7c08a7bd35373f3d9 Mon Sep 17 00:00:00 2001 From: Nick Colley Date: Wed, 13 May 2020 13:26:20 +0100 Subject: [PATCH 390/760] Update CircleCi Node.js version to 10.13 Gatsby has made a breaking change in a minor version so we need to update to avoid the build breaking. https://github.com/gatsbyjs/gatsby/pull/22400#issuecomment-627911322 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 93d2d57c0..48ffeb953 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ references: container_config_node8: &container_config_node8 working_directory: ~/project/build docker: - - image: circleci/node:8.16 + - image: circleci/node:10.13 workspace_root: &workspace_root ~/project From 98ae75bc55a50920bf274806b5b87c7e0853e9d3 Mon Sep 17 00:00:00 2001 From: Nick Colley Date: Thu, 14 May 2020 10:40:37 +0100 Subject: [PATCH 391/760] Ensure x-podcast-launchers has node-sass dependency. It's not clear why this has started failing but perhaps since these packages are built in parallel there was a race condition that we were not aware of. Hopefully by adding node-sass as a dependency to this package, which matches x-gift-article and x-follow-button, we'll resolve this issue and master will not fail. --- components/x-podcast-launchers/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/x-podcast-launchers/package.json b/components/x-podcast-launchers/package.json index 4a57cbdf6..92cb27107 100644 --- a/components/x-podcast-launchers/package.json +++ b/components/x-podcast-launchers/package.json @@ -24,7 +24,8 @@ }, "devDependencies": { "@financial-times/x-rollup": "file:../../packages/x-rollup", - "bower": "^1.8.8" + "bower": "^1.8.8", + "node-sass": "^4.9.2" }, "repository": { "type": "git", From c45b98e4f0e4cd3fbf2c7e5622a3ae7fc8088b65 Mon Sep 17 00:00:00 2001 From: Nick Colley Date: Thu, 14 May 2020 12:14:47 +0100 Subject: [PATCH 392/760] Bump cache keys to force node-sass rebuild. --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 48ffeb953..cc2448de9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,24 +22,24 @@ references: # cache_keys_root: &cache_keys_root keys: - - cache-root-v1-{{ .Branch }}-{{ checksum "./package.json" }} + - cache-root-v2-{{ .Branch }}-{{ checksum "./package.json" }} cache_keys_docs: &cache_keys_docs keys: - - cache-docs-v1-{{ .Branch }}-{{ checksum "./web/package.json" }} + - cache-docs-v2-{{ .Branch }}-{{ checksum "./web/package.json" }} # # Cache creation # create_cache_root: &create_cache_root save_cache: - key: cache-root-v1-{{ .Branch }}-{{ checksum "./package.json" }} + key: cache-root-v2-{{ .Branch }}-{{ checksum "./package.json" }} paths: - ./node_modules/ create_cache_docs: &create_cache_docs save_cache: - key: cache-docs-v1-{{ .Branch }}-{{ checksum "./web/package.json" }} + key: cache-docs-v2-{{ .Branch }}-{{ checksum "./web/package.json" }} paths: - ./web/node_modules/ From 29fc29d2ddfe9bbd7654ee4fa0ab1398b800927d Mon Sep 17 00:00:00 2001 From: Nick Colley Date: Thu, 14 May 2020 12:34:38 +0100 Subject: [PATCH 393/760] Update container_config_node version to 10 Since we updated from Node.js version 8 it no longer makes sense. --- .circleci/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cc2448de9..e118f4e35 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ references: # # Workspace # - container_config_node8: &container_config_node8 + container_config_node10: &container_config_node10 working_directory: ~/project/build docker: - image: circleci/node:10.13 @@ -86,7 +86,7 @@ references: jobs: build: - <<: *container_config_node8 + <<: *container_config_node10 steps: - checkout - run: @@ -106,7 +106,7 @@ jobs: - build test: - <<: *container_config_node8 + <<: *container_config_node10 steps: - *attach_workspace - run: @@ -114,7 +114,7 @@ jobs: command: make test publish: - <<: *container_config_node8 + <<: *container_config_node10 steps: - *attach_workspace - run: @@ -129,7 +129,7 @@ jobs: command: npx athloi publish -- --access=public prerelease: - <<: *container_config_node8 + <<: *container_config_node10 steps: - *attach_workspace - run: @@ -154,7 +154,7 @@ jobs: command: npx athloi -F ${TARGET_MODULE} publish -- --access=public --tag=pre-release deploy: - <<: *container_config_node8 + <<: *container_config_node10 steps: - *attach_workspace - add_ssh_keys: From 8ff373d15bf44c8b914a06ca8877cd5f4569e7a8 Mon Sep 17 00:00:00 2001 From: Maggie Allen Date: Fri, 15 May 2020 11:25:29 +0100 Subject: [PATCH 394/760] Remove references to n-image in x-dash docs --- components/x-teaser/readme.md | 2 -- docs/guides/migrating-to-x-teaser.md | 7 +------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index aa6c27a59..7a187643e 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -173,8 +173,6 @@ Property | Type | Notes `imageSize` | String | XS, Small, Medium, Large, XL or XXL `imageLazyLoad` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. -[nimg]: https://github.com/Financial-Times/n-image/ - #### Headshot Props Property | Type | Notes diff --git a/docs/guides/migrating-to-x-teaser.md b/docs/guides/migrating-to-x-teaser.md index c92d62c22..4d8472600 100644 --- a/docs/guides/migrating-to-x-teaser.md +++ b/docs/guides/migrating-to-x-teaser.md @@ -139,15 +139,10 @@ Teasers may be configured by providing attributes. Common use cases are provided ### 6. Image lazy loading (optional) -If you have implemented image lazy loading on your pages using [n-image] or [o-lazy-load] you can continue to use this functionality with x-teaser. Setting the `imageLazyload` property to `true` will instruct the component to render the image with a `data-src` property instead of a `src` property. If you need to set a specific class name to identify these images you can set the `imageLazyLoad` property to a string, which will be appended to list of image class names. +If you have implemented image lazy loading on your pages using [o-lazy-load] you can continue to use this functionality with x-teaser. Setting the `imageLazyload` property to `true` will instruct the component to render the image with a `data-src` property instead of a `src` property. If you need to set a specific class name to identify these images you can set the `imageLazyLoad` property to a string, which will be appended to list of image class names. ```handlebars {{{x package="x-teaser" component="Teaser" preset="SmallHeavy" imageLazyLoad="o-lazy-load"}}} - -{{{x package="x-teaser" component="Teaser" preset="SmallHeavy" imageLazyLoad="n-image--lazy-loading"}}} -``` - -[n-image]: https://github.com/Financial-Times/n-image [o-lazy-load]: https://github.com/Financial-Times/o-lazy-load/ From 9bf93e9c47294f4981363c4d69beb247be300f1f Mon Sep 17 00:00:00 2001 From: Oliver Turner Date: Sun, 10 May 2020 17:59:48 +0100 Subject: [PATCH 395/760] Create x-privacy-manager component --- .storybook/register-components.js | 1 + components/x-privacy-manager/.bowerrc | 8 + components/x-privacy-manager/.npmignore | 3 + components/x-privacy-manager/bower.json | 16 + .../x-privacy-manager/package-lock.json | 11476 ++++++++++++++++ components/x-privacy-manager/package.json | 42 + components/x-privacy-manager/readme.md | 43 + components/x-privacy-manager/rollup.js | 4 + .../src/__tests__/privacy-manager.test.jsx | 87 + components/x-privacy-manager/src/messages.jsx | 78 + .../x-privacy-manager/src/privacy-manager.jsx | 115 + .../src/privacy-manager.scss | 63 + .../x-privacy-manager/src/radio-btn.jsx | 31 + .../x-privacy-manager/src/radio-btn.scss | 83 + components/x-privacy-manager/stories/data.js | 5 + components/x-privacy-manager/stories/index.js | 20 + components/x-privacy-manager/stories/knobs.js | 35 + .../stories/story/consent-accepted.js | 19 + .../stories/story/consent-blocked.js | 19 + .../stories/story/consent-indeterminate.js | 19 + .../stories/story/save-failed.js | 22 + package.json | 1 + 22 files changed, 12190 insertions(+) create mode 100644 components/x-privacy-manager/.bowerrc create mode 100644 components/x-privacy-manager/.npmignore create mode 100644 components/x-privacy-manager/bower.json create mode 100644 components/x-privacy-manager/package-lock.json create mode 100644 components/x-privacy-manager/package.json create mode 100644 components/x-privacy-manager/readme.md create mode 100644 components/x-privacy-manager/rollup.js create mode 100644 components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx create mode 100644 components/x-privacy-manager/src/messages.jsx create mode 100644 components/x-privacy-manager/src/privacy-manager.jsx create mode 100644 components/x-privacy-manager/src/privacy-manager.scss create mode 100644 components/x-privacy-manager/src/radio-btn.jsx create mode 100644 components/x-privacy-manager/src/radio-btn.scss create mode 100644 components/x-privacy-manager/stories/data.js create mode 100644 components/x-privacy-manager/stories/index.js create mode 100644 components/x-privacy-manager/stories/knobs.js create mode 100644 components/x-privacy-manager/stories/story/consent-accepted.js create mode 100644 components/x-privacy-manager/stories/story/consent-blocked.js create mode 100644 components/x-privacy-manager/stories/story/consent-indeterminate.js create mode 100644 components/x-privacy-manager/stories/story/save-failed.js diff --git a/.storybook/register-components.js b/.storybook/register-components.js index b93d545fb..2da5f11fa 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -6,6 +6,7 @@ const components = [ require('../components/x-gift-article/stories'), require('../components/x-podcast-launchers/stories'), require('../components/x-teaser-timeline/stories'), + require('../components/x-privacy-manager/stories') ]; module.exports = components; diff --git a/components/x-privacy-manager/.bowerrc b/components/x-privacy-manager/.bowerrc new file mode 100644 index 000000000..39039a4a1 --- /dev/null +++ b/components/x-privacy-manager/.bowerrc @@ -0,0 +1,8 @@ +{ + "registry": { + "search": [ + "https://origami-bower-registry.ft.com", + "https://registry.bower.io" + ] + } +} diff --git a/components/x-privacy-manager/.npmignore b/components/x-privacy-manager/.npmignore new file mode 100644 index 000000000..a44a9e753 --- /dev/null +++ b/components/x-privacy-manager/.npmignore @@ -0,0 +1,3 @@ +src/ +stories/ +rollup.js diff --git a/components/x-privacy-manager/bower.json b/components/x-privacy-manager/bower.json new file mode 100644 index 000000000..9306e4659 --- /dev/null +++ b/components/x-privacy-manager/bower.json @@ -0,0 +1,16 @@ +{ + "name": "@financial-times/x-cookieconsent", + "description": "", + "main": "dist/cookie-consent.cjs.js", + "private": true, + "dependencies": { + "o-buttons": "^6.0.0", + "o-colors": "^5.0.0", + "o-grid": "^5.0.0", + "o-loading": "^4.0.0", + "o-message": "^4.0.0", + "o-normalise": "^2.0.0", + "o-spacing": "2.0.0", + "o-typography": "6.0.0" + } +} \ No newline at end of file diff --git a/components/x-privacy-manager/package-lock.json b/components/x-privacy-manager/package-lock.json new file mode 100644 index 000000000..5bf354b22 --- /dev/null +++ b/components/x-privacy-manager/package-lock.json @@ -0,0 +1,11476 @@ +{ + "name": "@financial-times/x-privacy-manager", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@financial-times/x-engine": { + "version": "file:../../packages/x-engine", + "requires": { + "assign-deep": "^1.0.0" + }, + "dependencies": { + "assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "requires": { + "assign-symbols": "^2.0.2" + } + }, + "assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" + } + } + }, + "@financial-times/x-interaction": { + "version": "file:../x-interaction", + "requires": { + "@financial-times/x-engine": "file:../../packages/x-engine", + "@quarterto/short-id": "^1.1.0" + }, + "dependencies": { + "@financial-times/x-engine": { + "version": "file:../../packages/x-engine", + "requires": { + "assign-deep": "^1.0.0" + }, + "dependencies": { + "assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "requires": { + "assign-symbols": "^2.0.2" + } + }, + "assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" + } + } + }, + "@financial-times/x-rollup": { + "requires": { + "@babel/core": "^7.6.4", + "@babel/plugin-external-helpers": "^7.2.0", + "@financial-times/x-babel-config": "file:../../packages/x-babel-config", + "chalk": "^2.4.2", + "log-symbols": "^3.0.0", + "rollup": "^1.23.0", + "rollup-plugin-babel": "^4.3.2", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-postcss": "^2.0.2" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", + "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.6", + "@babel/parser": "^7.9.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", + "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", + "requires": { + "@babel/types": "^7.9.6", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-transforms": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", + "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.9.0", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/helper-replace-supers": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", + "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + }, + "@babel/helpers": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", + "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==" + }, + "@babel/plugin-external-helpers": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.8.3.tgz", + "integrity": "sha512-mx0WXDDiIl5DwzMtzWGRSPugXi9BxROS05GQrhLNbEamhBiicgn994ibwkyiBH+6png7bm/yA7AUsvHyCXi4Vw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", + "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@financial-times/x-babel-config": { + "version": "file:../../packages/x-babel-config", + "requires": { + "@babel/plugin-transform-react-jsx": "^7.3.0", + "@babel/preset-env": "^7.4.3", + "babel-jest": "^24.0.0", + "fast-async": "^7.0.6" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/compat-data": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.6.tgz", + "integrity": "sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g==", + "requires": { + "browserslist": "^4.11.1", + "invariant": "^2.2.4", + "semver": "^5.5.0" + } + }, + "@babel/core": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", + "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.6", + "@babel/parser": "^7.9.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", + "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", + "requires": { + "@babel/types": "^7.9.6", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", + "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", + "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz", + "integrity": "sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/types": "^7.9.0" + } + }, + "@babel/helper-builder-react-jsx-experimental": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.5.tgz", + "integrity": "sha512-HAagjAC93tk748jcXpZ7oYRZH485RCq/+yEv9SIWezHRPv9moZArTnkUNciUNzvwHUABmiWKlcxJvMcu59UwTg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-module-imports": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz", + "integrity": "sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw==", + "requires": { + "@babel/compat-data": "^7.9.6", + "browserslist": "^4.11.1", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", + "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-regex": "^7.8.3", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-define-map": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", + "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", + "requires": { + "@babel/helper-function-name": "^7.8.3", + "@babel/types": "^7.8.3", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", + "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", + "requires": { + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-function-name": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", + "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-transforms": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", + "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.9.0", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/helper-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", + "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", + "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-wrap-function": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-replace-supers": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", + "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + }, + "@babel/helper-wrap-function": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", + "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", + "requires": { + "@babel/helper-function-name": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helpers": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", + "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", + "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", + "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", + "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", + "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz", + "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.9.5" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", + "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", + "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.8", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", + "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", + "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", + "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", + "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", + "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", + "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", + "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz", + "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-define-map": "^7.8.3", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-split-export-declaration": "^7.8.3", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", + "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz", + "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", + "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", + "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", + "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", + "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", + "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", + "requires": { + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", + "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", + "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz", + "integrity": "sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw==", + "requires": { + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz", + "integrity": "sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ==", + "requires": { + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-simple-access": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz", + "integrity": "sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg==", + "requires": { + "@babel/helper-hoist-variables": "^7.8.3", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", + "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", + "requires": { + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", + "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.3" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", + "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", + "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz", + "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==", + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", + "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz", + "integrity": "sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw==", + "requires": { + "@babel/helper-builder-react-jsx": "^7.9.0", + "@babel/helper-builder-react-jsx-experimental": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", + "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", + "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", + "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", + "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", + "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-regex": "^7.8.3" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", + "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", + "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", + "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/preset-env": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.6.tgz", + "integrity": "sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ==", + "requires": { + "@babel/compat-data": "^7.9.6", + "@babel/helper-compilation-targets": "^7.9.6", + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-proposal-async-generator-functions": "^7.8.3", + "@babel/plugin-proposal-dynamic-import": "^7.8.3", + "@babel/plugin-proposal-json-strings": "^7.8.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-numeric-separator": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.9.6", + "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.9.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.8.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.8.3", + "@babel/plugin-transform-async-to-generator": "^7.8.3", + "@babel/plugin-transform-block-scoped-functions": "^7.8.3", + "@babel/plugin-transform-block-scoping": "^7.8.3", + "@babel/plugin-transform-classes": "^7.9.5", + "@babel/plugin-transform-computed-properties": "^7.8.3", + "@babel/plugin-transform-destructuring": "^7.9.5", + "@babel/plugin-transform-dotall-regex": "^7.8.3", + "@babel/plugin-transform-duplicate-keys": "^7.8.3", + "@babel/plugin-transform-exponentiation-operator": "^7.8.3", + "@babel/plugin-transform-for-of": "^7.9.0", + "@babel/plugin-transform-function-name": "^7.8.3", + "@babel/plugin-transform-literals": "^7.8.3", + "@babel/plugin-transform-member-expression-literals": "^7.8.3", + "@babel/plugin-transform-modules-amd": "^7.9.6", + "@babel/plugin-transform-modules-commonjs": "^7.9.6", + "@babel/plugin-transform-modules-systemjs": "^7.9.6", + "@babel/plugin-transform-modules-umd": "^7.9.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", + "@babel/plugin-transform-new-target": "^7.8.3", + "@babel/plugin-transform-object-super": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.9.5", + "@babel/plugin-transform-property-literals": "^7.8.3", + "@babel/plugin-transform-regenerator": "^7.8.7", + "@babel/plugin-transform-reserved-words": "^7.8.3", + "@babel/plugin-transform-shorthand-properties": "^7.8.3", + "@babel/plugin-transform-spread": "^7.8.3", + "@babel/plugin-transform-sticky-regex": "^7.8.3", + "@babel/plugin-transform-template-literals": "^7.8.3", + "@babel/plugin-transform-typeof-symbol": "^7.8.4", + "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.9.6", + "browserslist": "^4.11.1", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", + "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", + "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", + "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", + "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "requires": { + "@jest/source-map": "^24.9.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/fake-timers": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", + "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "requires": { + "@jest/types": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0" + } + }, + "@jest/source-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", + "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@jest/test-result": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", + "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "requires": { + "@jest/console": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/transform": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", + "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.9.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.9.0", + "jest-regex-util": "^24.9.0", + "jest-util": "^24.9.0", + "micromatch": "^3.1.10", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/babel__core": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", + "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz", + "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==", + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", + "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" + }, + "@types/yargs": { + "version": "13.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz", + "integrity": "sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + }, + "ansi-styles": { + "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==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "babel-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", + "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "requires": { + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.9.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", + "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + } + } + }, + "babel-plugin-jest-hoist": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", + "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", + "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.9.0" + } + }, + "babylon": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "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==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "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==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browserslist": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", + "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "requires": { + "caniuse-lite": "^1.0.30001043", + "electron-to-chromium": "^1.3.413", + "node-releases": "^1.1.53", + "pkg-up": "^2.0.0" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "requires": { + "node-int64": "^0.4.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "caniuse-lite": { + "version": "1.0.30001054", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001054.tgz", + "integrity": "sha512-jiKlTI6Ur8Kjfj8z0muGrV6FscpRvefcQVPSuMuXnvRCfExU7zlVLNjmOz1TnurWgUrAY7MMmjyy+uTgIl1XHw==" + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "requires": { + "rsvp": "^4.8.4" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "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=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-js-compat": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", + "requires": { + "browserslist": "^4.8.5", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "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==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "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==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "electron-to-chromium": { + "version": "1.3.432", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.432.tgz", + "integrity": "sha512-/GdNhXyLP5Yl2322CUX/+Xi8NhdHBqL6lD9VJVKjH6CjoPGakvwZ5CpKgj/oOlbzuWWjOvMjDw1bBuAIRCNTlw==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==" + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "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==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "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==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-async": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/fast-async/-/fast-async-7.0.6.tgz", + "integrity": "sha512-/iUa3eSQC+Xh5tN6QcVLsEsN7b1DaPIoTZo++VpLLIxtdNW2tEmMZex4TcrMeRnBwMOpZwue2CB171wjt5Kgqg==", + "requires": { + "@babel/generator": "^7.0.0-beta.44", + "@babel/helper-module-imports": "^7.0.0-beta.44", + "babylon": "^7.0.0-beta.44", + "nodent-runtime": "^3.2.1", + "nodent-transform": "^3.2.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "requires": { + "bser": "2.1.1" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "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.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "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=", + "requires": { + "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=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, + "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=", + "requires": { + "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=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "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==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "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=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==" + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "jest-haste-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", + "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "requires": { + "@jest/types": "^24.9.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.9.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", + "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "requires": { + "@jest/types": "^24.9.0" + } + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==" + }, + "jest-serializer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", + "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==" + }, + "jest-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", + "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "requires": { + "@jest/console": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/source-map": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "requires": { + "leven": "^3.1.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "requires": { + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "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.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" + }, + "node-releases": { + "version": "1.1.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.55.tgz", + "integrity": "sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w==" + }, + "nodent-runtime": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/nodent-runtime/-/nodent-runtime-3.2.1.tgz", + "integrity": "sha512-7Ws63oC+215smeKJQCxzrK21VFVlCFBkwl0MOObt0HOpVQXs3u483sAmtkF33nNqZ5rSOQjB76fgyPBmAUrtCA==" + }, + "nodent-transform": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/nodent-transform/-/nodent-transform-3.2.9.tgz", + "integrity": "sha512-4a5FH4WLi+daH/CGD5o/JWRR8W5tlCkd3nrDSkxbOzscJTyTUITltvOJeQjg3HJ1YgEuNyiPhQbvbtRjkQBByQ==" + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "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=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "^2.1.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + } + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "requires": { + "util.promisify": "^1.0.0" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, + "regenerator-transform": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", + "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "requires": { + "@babel/runtime": "^7.8.4", + "private": "^0.1.8" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "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==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "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==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "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=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "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=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "requires": { + "makeerror": "1.0.x" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/estree": { + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", + "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==" + }, + "@types/node": { + "version": "13.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", + "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" + }, + "acorn": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", + "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "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==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "browserslist": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", + "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "requires": { + "caniuse-lite": "^1.0.30001043", + "electron-to-chromium": "^1.3.413", + "node-releases": "^1.1.53", + "pkg-up": "^2.0.0" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001054", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001054.tgz", + "integrity": "sha512-jiKlTI6Ur8Kjfj8z0muGrV6FscpRvefcQVPSuMuXnvRCfExU7zlVLNjmOz1TnurWgUrAY7MMmjyy+uTgIl1XHw==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-modules-loader-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", + "integrity": "sha1-WQhmgpShvs0mGuCkziGwtVHyHRY=", + "requires": { + "icss-replace-symbols": "1.1.0", + "postcss": "6.0.1", + "postcss-modules-extract-imports": "1.1.0", + "postcss-modules-local-by-default": "1.2.0", + "postcss-modules-scope": "1.1.0", + "postcss-modules-values": "1.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz", + "integrity": "sha1-AA29H47vIXqjaLmiEsX8QLKo8/I=", + "requires": { + "chalk": "^1.1.3", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "css-selector-tokenizer": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", + "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", + "requires": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2", + "regexpu-core": "^4.6.0" + } + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "css-what": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", + "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==" + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" + }, + "csso": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", + "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "requires": { + "css-tree": "1.0.0-alpha.39" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", + "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", + "requires": { + "mdn-data": "2.0.6", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", + "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "electron-to-chromium": { + "version": "1.3.432", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.432.tgz", + "integrity": "sha512-/GdNhXyLP5Yl2322CUX/+Xi8NhdHBqL6lD9VJVKjH6CjoPGakvwZ5CpKgj/oOlbzuWWjOvMjDw1bBuAIRCNTlw==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + }, + "eventemitter3": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", + "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==" + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "generic-names": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz", + "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==", + "requires": { + "loader-utils": "^1.1.0" + } + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" + }, + "import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "requires": { + "import-from": "^3.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + } + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-reference": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", + "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", + "requires": { + "@types/estree": "0.0.39" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + } + } + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "requires": { + "chalk": "^2.4.2" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-releases": { + "version": "1.1.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.55.tgz", + "integrity": "sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w==" + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-queue": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.4.0.tgz", + "integrity": "sha512-X7ddxxiQ+bLR/CUt3/BVKrGcJDNxBr0pEEFKHHB6vTPWNUhgDv36GpIH18RmGM3YGPpBT+JWGjDDqsVGuF0ERw==", + "requires": { + "eventemitter3": "^4.0.0", + "p-timeout": "^3.1.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "^2.1.0" + } + }, + "postcss": { + "version": "7.0.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.29.tgz", + "integrity": "sha512-ba0ApvR3LxGvRMMiUa9n0WR4HjzcYm7tS+ht4/2Nd0NLtHpPIH77fuB9Xh1/yJVz9O/E/95Y/dn8ygWsyffXtw==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-calc": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", + "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "dependencies": { + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "requires": { + "import-from": "^2.1.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "requires": { + "resolve-from": "^3.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-2.0.0.tgz", + "integrity": "sha512-eqp+Bva+U2cwQO7dECJ8/V+X+uH1HduNeITB0CPPFAu6d/8LKQ32/j+p9rQ2YL1QytVcrNU0X+fBqgGmQIA1Rw==", + "requires": { + "css-modules-loader-core": "^1.1.0", + "generic-names": "^2.0.1", + "lodash.camelcase": "^4.3.0", + "postcss": "^7.0.1", + "string-hash": "^1.1.1" + } + }, + "postcss-modules-extract-imports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", + "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "promise.series": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", + "integrity": "sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" + }, + "rollup": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "requires": { + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" + } + }, + "rollup-plugin-babel": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", + "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "requires": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-postcss": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-2.9.0.tgz", + "integrity": "sha512-Y7qDwlqjZMBexbB1kRJf+jKIQL8HR6C+ay53YzN+nNJ64hn1PNZfBE3c61hFUhD//zrMwmm7uBW30RuTi+CD0w==", + "requires": { + "chalk": "^4.0.0", + "concat-with-sourcemaps": "^1.1.0", + "cssnano": "^4.1.10", + "import-cwd": "^3.0.0", + "p-queue": "^6.3.0", + "pify": "^5.0.0", + "postcss": "^7.0.27", + "postcss-load-config": "^2.1.0", + "postcss-modules": "^2.0.0", + "promise.series": "^0.2.0", + "resolve": "^1.16.0", + "rollup-pluginutils": "^2.8.2", + "safe-identifier": "^0.4.1", + "style-inject": "^0.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "requires": { + "estree-walker": "^0.6.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-identifier": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.1.tgz", + "integrity": "sha512-73tOz5TXsq3apuCc3vC8c9QRhhdNZGiBhHmPPjqpH4TO5oCDqk8UIsDcSs/RG6dYcFAkOOva0pqHS3u7hh7XXA==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=" + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "style-inject": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", + "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==" + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" + } + } + }, + "@financial-times/x-test-utils": { + "requires": { + "enzyme": "^3.6.0", + "enzyme-adapter-react-16": "^1.5.0", + "jest-enzyme": "^6.0.4", + "react": "^16.5.0", + "react-dom": "^16.5.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/node": { + "version": "13.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", + "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" + }, + "abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" + }, + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" + }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" + } + } + }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" + }, + "airbnb-prop-types": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz", + "integrity": "sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA==", + "requires": { + "array.prototype.find": "^2.1.0", + "function.prototype.name": "^1.1.1", + "has": "^1.0.3", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.9.0" + } + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "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==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "array.prototype.find": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", + "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.4" + } + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "circular-json-es6": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", + "integrity": "sha512-ODYONMMNb3p658Zv+Pp+/XPa5s6q7afhz3Tzyvo+VRh9WIrJ64J76ZC4GQxnlye/NesTn09jvOiuE8+xxfpwhQ==" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "cssstyle": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "requires": { + "cssom": "0.3.x" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "deep-equal-ident": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz", + "integrity": "sha1-BvS4nlNxDNbOpKd4HHqVZkLejck=", + "requires": { + "lodash.isequal": "^3.0" + }, + "dependencies": { + "lodash.isequal": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-3.0.4.tgz", + "integrity": "sha1-HDXrO27wzR/1F0Pj6jz3/f/ay2Q=", + "requires": { + "lodash._baseisequal": "^3.0.0", + "lodash._bindcallback": "^3.0.0" + } + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "enzyme": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", + "requires": { + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", + "has": "^1.0.3", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.2.1" + } + }, + "enzyme-adapter-react-16": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz", + "integrity": "sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==", + "requires": { + "enzyme-adapter-utils": "^1.13.0", + "enzyme-shallow-equal": "^1.0.1", + "has": "^1.0.3", + "object.assign": "^4.1.0", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^16.12.0", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.7.0" + } + }, + "enzyme-adapter-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz", + "integrity": "sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ==", + "requires": { + "airbnb-prop-types": "^2.15.0", + "function.prototype.name": "^1.1.2", + "object.assign": "^4.1.0", + "object.fromentries": "^2.0.2", + "prop-types": "^15.7.2", + "semver": "^5.7.1" + } + }, + "enzyme-matchers": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-6.1.2.tgz", + "integrity": "sha512-cP9p+HMOZ1ZXQ+k2H4dCkxmTZzIvpEy5zv0ZjgoBl6D0U43v+bJGH5IeWHdIovCzgJ0dVcMCKJ6lNu83lYUCAA==", + "requires": { + "circular-json-es6": "^2.0.1", + "deep-equal-ident": "^1.1.1" + } + }, + "enzyme-shallow-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz", + "integrity": "sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==", + "requires": { + "has": "^1.0.3", + "object-is": "^1.0.2" + } + }, + "enzyme-to-json": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.4.4.tgz", + "integrity": "sha512-50LELP/SCPJJGic5rAARvU7pgE3m1YaNj7JLM+Qkhl5t7PAs6fiyc8xzc50RnkKPFQCv0EeFVjEWdIFRGPWMsA==", + "requires": { + "lodash": "^4.17.15", + "react-is": "^16.12.0" + } + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", + "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "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==", + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "^1.0.1" + } + }, + "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.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "function.prototype.name": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", + "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "functions-have-names": "^1.2.0" + } + }, + "functions-have-names": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", + "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "html-element-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", + "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", + "requires": { + "array-filter": "^1.0.0" + } + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jest-environment-enzyme": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-6.1.2.tgz", + "integrity": "sha512-WHeBKgBYOdryuOTEoK55lJwjg7Raery1OgXHLwukI3mSYgOkm2UrCDDT+vneqVgy7F8KuRHyStfD+TC/m2b7Kg==", + "requires": { + "jest-environment-jsdom": "^22.4.1" + } + }, + "jest-environment-jsdom": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", + "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", + "requires": { + "jest-mock": "^22.4.3", + "jest-util": "^22.4.3", + "jsdom": "^11.5.1" + } + }, + "jest-enzyme": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.1.2.tgz", + "integrity": "sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q==", + "requires": { + "enzyme-matchers": "^6.1.2", + "enzyme-to-json": "^3.3.0", + "jest-environment-enzyme": "^6.1.2" + } + }, + "jest-message-util": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", + "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", + "requires": { + "@babel/code-frame": "^7.0.0-beta.35", + "chalk": "^2.0.1", + "micromatch": "^2.3.11", + "slash": "^1.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", + "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==" + }, + "jest-util": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", + "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", + "requires": { + "callsites": "^2.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.11", + "is-ci": "^1.0.10", + "jest-message-util": "^22.4.3", + "mkdirp": "^0.5.1", + "source-map": "^0.6.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" + } + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash._baseisequal": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", + "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", + "requires": { + "lodash.isarray": "^3.0.0", + "lodash.istypedarray": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.istypedarray": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", + "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "moo": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", + "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==" + }, + "nearley": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.3.tgz", + "integrity": "sha512-FpAy1PmTsUpOtgxr23g4jRNvJHYzZEW2PixXeSzksLR/ykPfwKhAodc2+9wQhY+JneWLcvkDw6q7FJIsIdF/aQ==", + "requires": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6", + "semver": "^5.4.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "prop-types-exact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", + "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", + "requires": { + "has": "^1.0.3", + "object.assign": "^4.1.0", + "reflect.ownkeys": "^0.2.0" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + } + } + }, + "react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-dom": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", + "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-test-renderer": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.13.1.tgz", + "integrity": "sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==", + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "react-is": "^16.8.6", + "scheduler": "^0.19.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "reflect.ownkeys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", + "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=" + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "requires": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "requires": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "string.prototype.trim": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", + "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "requires": { + "punycode": "^2.1.0" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + } + } + }, + "@quarterto/short-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@quarterto/short-id/-/short-id-1.1.0.tgz", + "integrity": "sha1-m5EekORhSHqEDMBihpQUv7uvua0=" + } + } + }, + "@financial-times/x-rollup": { + "version": "file:../../packages/x-rollup", + "dev": true, + "requires": { + "@babel/core": "^7.6.4", + "@babel/plugin-external-helpers": "^7.2.0", + "@financial-times/x-babel-config": "file:../../packages/x-babel-config", + "chalk": "^2.4.2", + "log-symbols": "^3.0.0", + "rollup": "^1.23.0", + "rollup-plugin-babel": "^4.3.2", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-postcss": "^2.0.2" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", + "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.6", + "@babel/parser": "^7.9.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", + "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", + "requires": { + "@babel/types": "^7.9.6", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-transforms": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", + "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.9.0", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/helper-replace-supers": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", + "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + }, + "@babel/helpers": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", + "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==" + }, + "@babel/plugin-external-helpers": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.8.3.tgz", + "integrity": "sha512-mx0WXDDiIl5DwzMtzWGRSPugXi9BxROS05GQrhLNbEamhBiicgn994ibwkyiBH+6png7bm/yA7AUsvHyCXi4Vw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", + "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@financial-times/x-babel-config": { + "version": "file:../../packages/x-babel-config", + "requires": { + "@babel/plugin-transform-react-jsx": "^7.3.0", + "@babel/preset-env": "^7.4.3", + "babel-jest": "^24.0.0", + "fast-async": "^7.0.6" + }, + "dependencies": {} + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/estree": { + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", + "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==" + }, + "@types/node": { + "version": "13.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", + "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" + }, + "acorn": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", + "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "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==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "browserslist": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", + "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "requires": { + "caniuse-lite": "^1.0.30001043", + "electron-to-chromium": "^1.3.413", + "node-releases": "^1.1.53", + "pkg-up": "^2.0.0" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001054", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001054.tgz", + "integrity": "sha512-jiKlTI6Ur8Kjfj8z0muGrV6FscpRvefcQVPSuMuXnvRCfExU7zlVLNjmOz1TnurWgUrAY7MMmjyy+uTgIl1XHw==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": {} + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-modules-loader-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", + "integrity": "sha1-WQhmgpShvs0mGuCkziGwtVHyHRY=", + "requires": { + "icss-replace-symbols": "1.1.0", + "postcss": "6.0.1", + "postcss-modules-extract-imports": "1.1.0", + "postcss-modules-local-by-default": "1.2.0", + "postcss-modules-scope": "1.1.0", + "postcss-modules-values": "1.3.0" + }, + "dependencies": {} + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "css-selector-tokenizer": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", + "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", + "requires": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2", + "regexpu-core": "^4.6.0" + } + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": {} + }, + "css-what": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", + "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==" + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" + }, + "csso": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", + "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "requires": { + "css-tree": "1.0.0-alpha.39" + }, + "dependencies": {} + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": {} + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "electron-to-chromium": { + "version": "1.3.432", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.432.tgz", + "integrity": "sha512-/GdNhXyLP5Yl2322CUX/+Xi8NhdHBqL6lD9VJVKjH6CjoPGakvwZ5CpKgj/oOlbzuWWjOvMjDw1bBuAIRCNTlw==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + }, + "eventemitter3": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", + "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==" + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "generic-names": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz", + "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==", + "requires": { + "loader-utils": "^1.1.0" + } + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" + }, + "import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "requires": { + "import-from": "^3.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": {} + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-reference": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", + "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", + "requires": { + "@types/estree": "0.0.39" + }, + "dependencies": {} + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": {} + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "requires": { + "chalk": "^2.4.2" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-releases": { + "version": "1.1.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.55.tgz", + "integrity": "sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w==" + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-queue": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.4.0.tgz", + "integrity": "sha512-X7ddxxiQ+bLR/CUt3/BVKrGcJDNxBr0pEEFKHHB6vTPWNUhgDv36GpIH18RmGM3YGPpBT+JWGjDDqsVGuF0ERw==", + "requires": { + "eventemitter3": "^4.0.0", + "p-timeout": "^3.1.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "^2.1.0" + } + }, + "postcss": { + "version": "7.0.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.29.tgz", + "integrity": "sha512-ba0ApvR3LxGvRMMiUa9n0WR4HjzcYm7tS+ht4/2Nd0NLtHpPIH77fuB9Xh1/yJVz9O/E/95Y/dn8ygWsyffXtw==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": {} + }, + "postcss-calc": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", + "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "dependencies": {} + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": {} + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": {} + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": {} + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-2.0.0.tgz", + "integrity": "sha512-eqp+Bva+U2cwQO7dECJ8/V+X+uH1HduNeITB0CPPFAu6d/8LKQ32/j+p9rQ2YL1QytVcrNU0X+fBqgGmQIA1Rw==", + "requires": { + "css-modules-loader-core": "^1.1.0", + "generic-names": "^2.0.1", + "lodash.camelcase": "^4.3.0", + "postcss": "^7.0.1", + "string-hash": "^1.1.1" + } + }, + "postcss-modules-extract-imports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", + "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": {} + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": {} + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": {} + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + }, + "dependencies": {} + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": {} + }, + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": {} + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "promise.series": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", + "integrity": "sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": {} + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" + }, + "rollup": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "requires": { + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" + } + }, + "rollup-plugin-babel": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", + "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "requires": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-postcss": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-2.9.0.tgz", + "integrity": "sha512-Y7qDwlqjZMBexbB1kRJf+jKIQL8HR6C+ay53YzN+nNJ64hn1PNZfBE3c61hFUhD//zrMwmm7uBW30RuTi+CD0w==", + "requires": { + "chalk": "^4.0.0", + "concat-with-sourcemaps": "^1.1.0", + "cssnano": "^4.1.10", + "import-cwd": "^3.0.0", + "p-queue": "^6.3.0", + "pify": "^5.0.0", + "postcss": "^7.0.27", + "postcss-load-config": "^2.1.0", + "postcss-modules": "^2.0.0", + "promise.series": "^0.2.0", + "resolve": "^1.16.0", + "rollup-pluginutils": "^2.8.2", + "safe-identifier": "^0.4.1", + "style-inject": "^0.3.0" + }, + "dependencies": {} + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "requires": { + "estree-walker": "^0.6.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-identifier": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.1.tgz", + "integrity": "sha512-73tOz5TXsq3apuCc3vC8c9QRhhdNZGiBhHmPPjqpH4TO5oCDqk8UIsDcSs/RG6dYcFAkOOva0pqHS3u7hh7XXA==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": {} + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=" + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "style-inject": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", + "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==" + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": {} + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" + } + } + }, + "@financial-times/x-test-utils": { + "version": "file:../../packages/x-test-utils", + "dev": true, + "requires": { + "enzyme": "^3.6.0", + "enzyme-adapter-react-16": "^1.5.0", + "jest-enzyme": "^6.0.4", + "react": "^16.5.0", + "react-dom": "^16.5.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/node": { + "version": "13.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", + "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" + }, + "abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" + }, + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" + }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": {} + }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" + }, + "airbnb-prop-types": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz", + "integrity": "sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA==", + "requires": { + "array.prototype.find": "^2.1.0", + "function.prototype.name": "^1.1.1", + "has": "^1.0.3", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.9.0" + } + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "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==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "array.prototype.find": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", + "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.4" + } + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "circular-json-es6": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", + "integrity": "sha512-ODYONMMNb3p658Zv+Pp+/XPa5s6q7afhz3Tzyvo+VRh9WIrJ64J76ZC4GQxnlye/NesTn09jvOiuE8+xxfpwhQ==" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "cssstyle": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "requires": { + "cssom": "0.3.x" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": {} + }, + "deep-equal-ident": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz", + "integrity": "sha1-BvS4nlNxDNbOpKd4HHqVZkLejck=", + "requires": { + "lodash.isequal": "^3.0" + }, + "dependencies": {} + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "enzyme": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", + "requires": { + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", + "has": "^1.0.3", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.2.1" + } + }, + "enzyme-adapter-react-16": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz", + "integrity": "sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==", + "requires": { + "enzyme-adapter-utils": "^1.13.0", + "enzyme-shallow-equal": "^1.0.1", + "has": "^1.0.3", + "object.assign": "^4.1.0", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^16.12.0", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.7.0" + } + }, + "enzyme-adapter-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz", + "integrity": "sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ==", + "requires": { + "airbnb-prop-types": "^2.15.0", + "function.prototype.name": "^1.1.2", + "object.assign": "^4.1.0", + "object.fromentries": "^2.0.2", + "prop-types": "^15.7.2", + "semver": "^5.7.1" + } + }, + "enzyme-matchers": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-6.1.2.tgz", + "integrity": "sha512-cP9p+HMOZ1ZXQ+k2H4dCkxmTZzIvpEy5zv0ZjgoBl6D0U43v+bJGH5IeWHdIovCzgJ0dVcMCKJ6lNu83lYUCAA==", + "requires": { + "circular-json-es6": "^2.0.1", + "deep-equal-ident": "^1.1.1" + } + }, + "enzyme-shallow-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz", + "integrity": "sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==", + "requires": { + "has": "^1.0.3", + "object-is": "^1.0.2" + } + }, + "enzyme-to-json": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.4.4.tgz", + "integrity": "sha512-50LELP/SCPJJGic5rAARvU7pgE3m1YaNj7JLM+Qkhl5t7PAs6fiyc8xzc50RnkKPFQCv0EeFVjEWdIFRGPWMsA==", + "requires": { + "lodash": "^4.17.15", + "react-is": "^16.12.0" + } + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", + "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "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==", + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "^1.0.1" + } + }, + "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.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "function.prototype.name": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", + "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "functions-have-names": "^1.2.0" + } + }, + "functions-have-names": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", + "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "html-element-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", + "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", + "requires": { + "array-filter": "^1.0.0" + } + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jest-environment-enzyme": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-6.1.2.tgz", + "integrity": "sha512-WHeBKgBYOdryuOTEoK55lJwjg7Raery1OgXHLwukI3mSYgOkm2UrCDDT+vneqVgy7F8KuRHyStfD+TC/m2b7Kg==", + "requires": { + "jest-environment-jsdom": "^22.4.1" + } + }, + "jest-environment-jsdom": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", + "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", + "requires": { + "jest-mock": "^22.4.3", + "jest-util": "^22.4.3", + "jsdom": "^11.5.1" + } + }, + "jest-enzyme": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.1.2.tgz", + "integrity": "sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q==", + "requires": { + "enzyme-matchers": "^6.1.2", + "enzyme-to-json": "^3.3.0", + "jest-environment-enzyme": "^6.1.2" + } + }, + "jest-message-util": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", + "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", + "requires": { + "@babel/code-frame": "^7.0.0-beta.35", + "chalk": "^2.0.1", + "micromatch": "^2.3.11", + "slash": "^1.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", + "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==" + }, + "jest-util": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", + "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", + "requires": { + "callsites": "^2.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.11", + "is-ci": "^1.0.10", + "jest-message-util": "^22.4.3", + "mkdirp": "^0.5.1", + "source-map": "^0.6.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": {} + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash._baseisequal": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", + "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", + "requires": { + "lodash.isarray": "^3.0.0", + "lodash.istypedarray": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.istypedarray": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", + "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "moo": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", + "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==" + }, + "nearley": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.3.tgz", + "integrity": "sha512-FpAy1PmTsUpOtgxr23g4jRNvJHYzZEW2PixXeSzksLR/ykPfwKhAodc2+9wQhY+JneWLcvkDw6q7FJIsIdF/aQ==", + "requires": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6", + "semver": "^5.4.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "prop-types-exact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", + "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", + "requires": { + "has": "^1.0.3", + "object.assign": "^4.1.0", + "reflect.ownkeys": "^0.2.0" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": {} + }, + "react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-dom": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", + "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-test-renderer": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.13.1.tgz", + "integrity": "sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==", + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "react-is": "^16.8.6", + "scheduler": "^0.19.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "reflect.ownkeys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", + "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=" + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "requires": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "requires": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "string.prototype.trim": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", + "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "requires": { + "punycode": "^2.1.0" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + } + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true + } + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "bower": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.8.tgz", + "integrity": "sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", + "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fetch-mock": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.9.0.tgz", + "integrity": "sha512-2/0FJo23c6xTYQqBKhp2t1kEeJqWcoPCTMDShJx4CS3w0Vjdi2/VO6nKVrcMJyurQEIvZBHZpdIWYdHvLTiA4A==", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^3.0.0", + "debug": "^4.1.1", + "glob-to-regexp": "^0.4.0", + "is-subset": "^0.1.1", + "lodash.isequal": "^4.5.0", + "path-to-regexp": "^2.2.1", + "querystring": "^0.2.0", + "whatwg-url": "^6.5.0" + } + }, + "fetch-mock-jest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fetch-mock-jest/-/fetch-mock-jest-1.3.0.tgz", + "integrity": "sha512-3hHLcSQrww8yhGQnUyKSCQik4piaWcjHc4/bdDfKCQI6GEOsOKBWJro/XBXGnZjZPy47cVtBDx99aQxNYK0/OA==", + "dev": true, + "requires": { + "fetch-mock": "^9.0.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "path-to-regexp": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz", + "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "sass": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.5.tgz", + "integrity": "sha512-FG2swzaZUiX53YzZSjSakzvGtlds0lcbF+URuU9mxOv7WBh7NhXEVDa4kPKN4hN6fC2TkOTOKqiqp6d53N9X5Q==", + "dev": true, + "requires": { + "chokidar": ">=2.0.0 <4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } +} diff --git a/components/x-privacy-manager/package.json b/components/x-privacy-manager/package.json new file mode 100644 index 000000000..cdbb38d77 --- /dev/null +++ b/components/x-privacy-manager/package.json @@ -0,0 +1,42 @@ +{ + "name": "@financial-times/x-privacy-manager", + "version": "0.0.0", + "description": "", + "main": "dist/privacy-manager.cjs.js", + "module": "dist/privacy-manager.esm.js", + "browser": "dist/privacy-manager.es5.js", + "style": "dist/privacy-manager.css", + "keywords": [ + "x-dash" + ], + "author": "", + "license": "ISC", + "repository": { + "type": "git", + "url": "https://github.com/Financial-Times/x-dash.git" + }, + "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-privacy-manager", + "engines": { + "node": ">= 6.0.0" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@financial-times/x-engine": "file:../../packages/x-engine", + "@financial-times/x-interaction": "file:../x-interaction", + "classnames": "2.2.6" + }, + "devDependencies": { + "@financial-times/x-rollup": "file:../../packages/x-rollup", + "@financial-times/x-test-utils": "file:../../packages/x-test-utils", + "bower": "1.8.8", + "fetch-mock-jest": "1.3.0", + "sass": "1.26.5" + }, + "scripts": { + "prepare": "bower install && npm run build", + "build": "node rollup.js", + "start": "node rollup.js --watch" + } +} diff --git a/components/x-privacy-manager/readme.md b/components/x-privacy-manager/readme.md new file mode 100644 index 000000000..c7c088e5f --- /dev/null +++ b/components/x-privacy-manager/readme.md @@ -0,0 +1,43 @@ +# x-cookieconsent + +This module has these features and scope. + + +## Installation + +This module is compatible with Node 6+ and is distributed on npm. + +```bash +npm install --save @financial-times/x-cookieconsent +``` + +The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. + +[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine + + +## Usage + +The components provided by this module are all functions that expect a map of [properties](#properties). They can be used with vanilla JavaScript or JSX (If you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: + +```jsx +import React from 'react'; +import { Cookie-consent } from '@financial-times/x-cookieconsent'; + +// A == B == C +const a = Cookie-consent(props); +const b = ; +const c = React.createElement(Cookie-consent, props); +``` + +All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. + +[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ + +### Properties + +Feature | Type | Notes +-----------------|--------|---------------------------- +`propertyName1` | String | +`propertyName2` | String | +`propertyName2` | String | diff --git a/components/x-privacy-manager/rollup.js b/components/x-privacy-manager/rollup.js new file mode 100644 index 000000000..d8f76868e --- /dev/null +++ b/components/x-privacy-manager/rollup.js @@ -0,0 +1,4 @@ +const xRollup = require('@financial-times/x-rollup'); +const pkg = require('./package.json'); + +xRollup({ input: './src/privacy-manager.jsx', pkg }); diff --git a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx new file mode 100644 index 000000000..e3f1b009d --- /dev/null +++ b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx @@ -0,0 +1,87 @@ +const { h } = require('@financial-times/x-engine'); +const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { Response } = require('node-fetch'); + +import { BasePrivacyManager, PrivacyManager } from '../privacy-manager'; + +describe('x-cookieconsent', () => { + describe('initial state', () => { + it('defaults to "Allow"', () => { + const subject = mount(); + const input = subject.find('input[value="true"]'); + expect(input.first().prop('checked')).toBe(true); + }); + + it('highlights explicitly set consent correctly: false', () => { + const subject = mount(); + const input = subject.find('input[value="false"]').first(); + expect(input.prop('checked')).toBe(true); + }); + + it('highlights explicitly set consent correctly: true', () => { + const subject = mount(); + const input = subject.find('input[value="true"]').first(); + expect(input.prop('checked')).toBe(true); + }); + }); + + describe('It displays the appropriate messaging', () => { + const defaultProps = { + consent: true, + referrer: 'www.ft.com', + legislation: ['ccpa'], + actions: { + onConsentChange: jest.fn(), + onSubmit: jest.fn().mockReturnValue({ _response: new Response() }) + }, + isLoading: false, + _response: undefined + }; + + it('None by default', () => { + const subject = mount(); + + const messages = subject.find('[data-o-component="o-message"]'); + expect(messages).toHaveLength(0); + }); + + it('While loading', () => { + const props = { ...defaultProps, isLoading: true }; + const subject = mount(); + + const messages = subject.find('[data-o-component="o-message"]'); + expect(messages).toHaveLength(1); + expect(messages.first()).toHaveClassName('o-message--neutral'); + }); + + it('On receiving a response with a status of 200', () => { + const _response = new Response('', { status: 200 }); + const props = { ...defaultProps, _response }; + const subject = mount(); + + const messages = subject.find('[data-o-component="o-message"]'); + const message = messages.first(); + + expect(messages).toHaveLength(1); + expect(message).toHaveClassName('o-message--success'); + + const link = message.find('[data-component="referrer-link"]'); + expect(link).toHaveProp('href', 'https://www.ft.com/'); + }); + + it('On receiving a response with a non-200 status', () => { + const _response = new Response('', { status: 400 }); + const props = { ...defaultProps, _response }; + const subject = mount(); + + const messages = subject.find('[data-o-component="o-message"]'); + const message = messages.first(); + + expect(messages).toHaveLength(1); + expect(message).toHaveClassName('o-message--error'); + + const link = message.find('[data-component="referrer-link"]'); + expect(link).toHaveProp('href', 'https://www.ft.com/'); + }); + }); +}); diff --git a/components/x-privacy-manager/src/messages.jsx b/components/x-privacy-manager/src/messages.jsx new file mode 100644 index 000000000..08b3a61b0 --- /dev/null +++ b/components/x-privacy-manager/src/messages.jsx @@ -0,0 +1,78 @@ +import { h } from '@financial-times/x-engine'; +import s from './privacy-manager.scss'; + +/** + * Provide a way to return to the referrer's homepage + * Potentially a Specialist Title, FT.com or FT App + * + * @param {string} referrer + */ +function renderReferrerLink(referrer) { + let url; + + try { + url = new URL(`https://${referrer}`); + } catch (err) { + // referrer cannot be parsed: omit link + return; + } + + return ( + + Continue to homepage + + ); +} + +function Message({ cls, children }) { + return ( +
      +
      +
      +
      {children}
      +
      +
      +
      + ); +} + +/** + * + * @param {{ + * success: boolean + * referrer: string + * }} props + */ +export function ResponseMessage({ success, referrer }) { + const statusDict = { + true: { + cls: 'o-message--success', + msg: 'Your setting has been saved.' + }, + false: { + cls: 'o-message--error', + msg: 'Your setting could not be saved. Please try again later.' + } + }; + + const status = statusDict[success]; + const cls = `o-message o-message--alert ${status.cls}`; + + return ( + + {status.msg} {renderReferrerLink(referrer)} + + ); +} + +export function LoadingMessage() { + const cls = 'o-message o-message--neutral'; + const spinnerCls = `o-loading o-loading--dark o-loading--small ${s['v-middle']}`; + + return ( + +
      + Loading... +
      + ); +} diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx new file mode 100644 index 000000000..fb467e230 --- /dev/null +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -0,0 +1,115 @@ +/** + * @typedef { import('react').FormEvent } SubmitEvent + */ + +import { h } from '@financial-times/x-engine'; +import { withActions } from '@financial-times/x-interaction'; + +import s from './privacy-manager.scss'; +import { RadioBtn } from './radio-btn'; +import { LoadingMessage, ResponseMessage } from './messages'; + +// TODO: Replace with the genuine endpoint +export const CONSENT_API = 'https://consent.ft.com'; + +export const withCustomActions = withActions(() => ({ + onConsentChange() { + return ({ consent = true }) => ({ consent: !consent }); + }, + + /** + * @param {SubmitEvent} event + */ + onSubmit(event) { + event.preventDefault(); + + return async ({ consent }) => { + const body = JSON.stringify({ + demographic: consent, + behavioural: consent, + programmatic: consent + }); + + try { + const _response = await fetch(CONSENT_API, { method: 'PATCH', body }); + return { _response }; + } catch (err) { + return { _response: { ok: false } }; + } + }; + } +})); + +/** + * @param {boolean} isLoading + * @param {Response} response + * @param {string} referrer + */ +function renderMessage(isLoading, response, referrer) { + if (isLoading) return ; + if (response) return ; + return null; +} + +/** + * @param {{ + * consent?: boolean + * referrer?: string + * legislation?: string[] + * actions: { + * onConsentChange: () => void + * onSubmit: (event?: SubmitEvent) => Promise<{_response: Response}> + * }, + * isLoading: boolean + * _response: Response | undefined + * }} Props + */ +export function BasePrivacyManager({ + consent = true, + referrer, + actions, + isLoading, + _response = undefined +}) { + return ( +
      +

      Do Not Sell My Personal Information

      +
      +

      + CCPA defines the "sale" of personal information in extremely broad terms. For this reason, + the collection of data by advertisers who advertise on our Sites for the purposes of + measuring the effectiveness of their advertising is caught, and requires the ability to + object to the use of your personal information. The data collected by advertisers may + include online identifiers, such as IP address, and interactions with advertisements. +

      +

      + Our advertising Terms and Conditions limit the use of this data by advertisers only to + their own business purposes, including analytics and attribution, not for commercial + purposes. +

      +
      + {renderMessage(isLoading, _response, referrer)} +
      +

      Use of my personal information for advertising purposes

      +
      + + Allow + See personalised adverts + + + Block + Only see generic adverts + +
      + +
      +
      +
      + ); +} + +const PrivacyManager = withCustomActions(BasePrivacyManager); + +export { PrivacyManager }; diff --git a/components/x-privacy-manager/src/privacy-manager.scss b/components/x-privacy-manager/src/privacy-manager.scss new file mode 100644 index 000000000..a6f9be10c --- /dev/null +++ b/components/x-privacy-manager/src/privacy-manager.scss @@ -0,0 +1,63 @@ +@import 'o-buttons/main'; +@import 'o-colors/main'; +@import 'o-grid/main'; +@import 'o-spacing/main'; +@import 'o-typography/main'; + +.v-middle { + display: inline-block; + vertical-align: middle; +} + +.loading { + composes: v-middle; + margin-left: oSpacingByName(s4) +} + +.consent { + @include oTypographySans($scale: 0, $line-height: 1.6); + + margin: auto; + max-width: map-get($o-grid-layouts, M); +} + +.consent__title { + @include oTypographyHeading($level: 1); +} + +.consent__copy { + margin-top: oSpacingByName(s8); +} + +.divider { + margin-top: oSpacingByName(s8); +} + +.form__title { + @include oTypographyHeading($level: 4); + + margin-top: oSpacingByName(s8); + text-align: center; +} + +.form__controls { + @include oGridRespondTo($from: S) { + display: flex; + } + + margin-top: oSpacingByName(s6); +} + +.form__submit { + @include oButtonsContent( + $opts: ( + 'type': 'primary', + 'size': 'big' + ) + ); + + display: block; + margin: oSpacingByName(s8) auto 0; + padding-left: oSpacingByName(m12); + padding-right: oSpacingByName(m12); +} diff --git a/components/x-privacy-manager/src/radio-btn.jsx b/components/x-privacy-manager/src/radio-btn.jsx new file mode 100644 index 000000000..81389ace3 --- /dev/null +++ b/components/x-privacy-manager/src/radio-btn.jsx @@ -0,0 +1,31 @@ +import { h } from '@financial-times/x-engine'; + +import s from './radio-btn.scss'; + +export function RadioBtn({ value, checked, onChange, children }) { + const id = `ccpa-${value}`; + + return ( +
      + + +
      + ); +} diff --git a/components/x-privacy-manager/src/radio-btn.scss b/components/x-privacy-manager/src/radio-btn.scss new file mode 100644 index 000000000..e49c16830 --- /dev/null +++ b/components/x-privacy-manager/src/radio-btn.scss @@ -0,0 +1,83 @@ +@import 'o-colors/main'; +@import 'o-grid/main'; +@import 'o-normalise/main'; +@import 'o-spacing/main'; +@import 'o-typography/main'; + +.input { + @include oNormaliseVisuallyHidden; +} + +.control { + flex: 1; + + & + .control { + margin-top: oSpacingByName(s4); + + @include oGridRespondTo($from: S) { + margin-top: 0; + margin-left: oSpacingByName(s4); + } + } +} + +.label { + transition: background-color 0.1s ease-in, color 0.1s ease-in; + + display: flex; + align-items: center; + + padding: oSpacingByName(s6) oSpacingByName(s4); + border: 2px solid oColorsByName('teal'); + cursor: pointer; + background-color: oColorsByName('white'); + + .input:checked + & { + background-color: oColorsByName('teal'); + color: oColorsByName('white'); + } + + .input:focus + & { + outline: 2px solid #7aacfe; + outline: 5px auto -webkit-focus-ring-color; + background-color: oColorsByName('teal-40'); + } +} + + +.label__text { + flex: 1; + + margin-right: oSpacingByName(s2); + + & > strong { + display: block; + font-size: 1.2rem; + font-weight: 600; + } + + & > span { + display: block; + margin-top: oSpacingByName(s1); + } +} + +.label__icon { + $icon-size: 28px; + width: $icon-size; + height: $icon-size; +} + +.label__icon__outer { + stroke: currentColor; + stroke-width: 3px; + fill: transparent; +} + +.label__icon__inner { + fill: transparent; + + .input:checked + .label & { + fill: currentColor; + } +} diff --git a/components/x-privacy-manager/stories/data.js b/components/x-privacy-manager/stories/data.js new file mode 100644 index 000000000..77fcb30f1 --- /dev/null +++ b/components/x-privacy-manager/stories/data.js @@ -0,0 +1,5 @@ +module.exports = { + consent: true, + legislation: [], + referrer: "ft.com" +} \ No newline at end of file diff --git a/components/x-privacy-manager/stories/index.js b/components/x-privacy-manager/stories/index.js new file mode 100644 index 000000000..5aee23f4e --- /dev/null +++ b/components/x-privacy-manager/stories/index.js @@ -0,0 +1,20 @@ +const { PrivacyManager } = require('../src/privacy-manager'); + +exports.component = PrivacyManager; + +exports.package = require('../package.json'); + +exports.dependencies = { + 'o-loading': '^4.0.0', + 'o-message': '^4.0.0', + 'o-typography': '^6.0.0', +}; + +exports.stories = [ + require('./story/consent-indeterminate'), + require('./story/consent-accepted'), + require('./story/consent-blocked'), + require('./story/save-failed'), +]; + +exports.knobs = require('./knobs'); diff --git a/components/x-privacy-manager/stories/knobs.js b/components/x-privacy-manager/stories/knobs.js new file mode 100644 index 000000000..43b46ffd0 --- /dev/null +++ b/components/x-privacy-manager/stories/knobs.js @@ -0,0 +1,35 @@ +const legislation = { + CCPA: ['ccpa', 'gdpr'] + // GDPR: ['gdpr'] +}; + +const referrers = { + 'ft.com': 'www.ft.com', + 'exec-appointments.com': 'www.exec-appointments.com', + 'fdibenchmark.com': 'www.fdibenchmark.com', + 'fdiintelligence.com': 'www.fdiintelligence.com', + 'fdimarkets.com': 'www.fdimarkets.com', + 'fdireports.com': 'www.fdireports.com', + 'ftadviser.com': 'www.ftadviser.com', + 'ftconfidentialresearch.com': 'www.ftconfidentialresearch.com', + 'globalriskregulator.com': 'www.globalriskregulator.com', + 'investorschronicle.co.uk': 'www.investorschronicle.co.uk', + 'non-execs.com': 'www.non-execs.com', + 'pensions-expert.com': 'www.pensions-expert.com', + 'pwmnet.com': 'www.pwmnet.com', + 'thebanker.com': 'www.thebanker.com', + 'thebankerdatabase.com': 'www.thebankerdatabase.com', + Undefined: '' +}; + +module.exports = (data, { boolean, select }) => ({ + consent() { + return boolean('Consent', data.consent); + }, + legislation() { + return select('Legislation', legislation, legislation['CCPA']); + }, + referrer() { + return select('Referrer', referrers, referrers['ft.com']); + } +}); diff --git a/components/x-privacy-manager/stories/story/consent-accepted.js b/components/x-privacy-manager/stories/story/consent-accepted.js new file mode 100644 index 000000000..9601714f3 --- /dev/null +++ b/components/x-privacy-manager/stories/story/consent-accepted.js @@ -0,0 +1,19 @@ +const data = require('../data'); +const { CONSENT_API } = require('../../src/privacy-manager'); + +exports.title = 'Consent: accepted'; + +exports.data = { + ...data, + consent: true +}; + +exports.knobs = Object.keys(exports.data); + +exports.fetchMock = (fetchMock) => { + fetchMock.mock(CONSENT_API, 200, { delay: 1000 }); +}; + +// This reference is only required for hot module loading in development +// +exports.m = module; diff --git a/components/x-privacy-manager/stories/story/consent-blocked.js b/components/x-privacy-manager/stories/story/consent-blocked.js new file mode 100644 index 000000000..5f829b09d --- /dev/null +++ b/components/x-privacy-manager/stories/story/consent-blocked.js @@ -0,0 +1,19 @@ +const data = require('../data'); +const { CONSENT_API } = require('../../src/privacy-manager'); + +exports.title = 'Consent: blocked'; + +exports.data = { + ...data, + consent: false +}; + +exports.knobs = Object.keys(exports.data); + +exports.fetchMock = (fetchMock) => { + fetchMock.mock(CONSENT_API, 200, { delay: 1000 }); +}; + +// This reference is only required for hot module loading in development +// +exports.m = module; diff --git a/components/x-privacy-manager/stories/story/consent-indeterminate.js b/components/x-privacy-manager/stories/story/consent-indeterminate.js new file mode 100644 index 000000000..6573749fd --- /dev/null +++ b/components/x-privacy-manager/stories/story/consent-indeterminate.js @@ -0,0 +1,19 @@ +const data = require('../data'); +const { CONSENT_API } = require('../../src/privacy-manager'); + +exports.title = 'Consent: indeterminate'; + +exports.data = { + ...data, + consent: undefined +}; + +exports.knobs = Object.keys(exports.data); + +exports.fetchMock = (fetchMock) => { + fetchMock.mock(CONSENT_API, 200, { delay: 1000 }); +}; + +// This reference is only required for hot module loading in development +// +exports.m = module; diff --git a/components/x-privacy-manager/stories/story/save-failed.js b/components/x-privacy-manager/stories/story/save-failed.js new file mode 100644 index 000000000..b76fa928e --- /dev/null +++ b/components/x-privacy-manager/stories/story/save-failed.js @@ -0,0 +1,22 @@ +const data = require('../data'); +const { CONSENT_API } = require('../../src/privacy-manager'); + +exports.title = 'Save failed'; + +exports.data = { + ...data, + consent: true +}; + +exports.knobs = Object.keys(exports.data); + +exports.fetchMock = (fetchMock) => { + fetchMock.patch(CONSENT_API, 500, { + throw: new Error('bad membership api'), + delay: 1000 + }); +}; + +// This reference is only required for hot module loading in development +// +exports.m = module; diff --git a/package.json b/package.json index 8297427d7..0d256c16a 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@storybook/addon-knobs": "^5.1.8", "@storybook/addon-viewport": "^5.1.8", "@storybook/react": "^5.1.8", + "@types/jest": "25.2.1", "babel-loader": "^8.0.4", "copy-webpack-plugin": "^5.0.2", "core-js": "^2.6.8", From bd1fc9f37cc72801ce4e226a4bf6027dce436e6a Mon Sep 17 00:00:00 2001 From: Oliver Turner Date: Mon, 18 May 2020 17:03:27 +0100 Subject: [PATCH 396/760] Stub out README --- components/x-privacy-manager/bower.json | 4 +-- components/x-privacy-manager/docs/ccpa.png | Bin 0 -> 86550 bytes components/x-privacy-manager/readme.md | 29 ++++++++++-------- .../src/__tests__/privacy-manager.test.jsx | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 components/x-privacy-manager/docs/ccpa.png diff --git a/components/x-privacy-manager/bower.json b/components/x-privacy-manager/bower.json index 9306e4659..7dab1c91b 100644 --- a/components/x-privacy-manager/bower.json +++ b/components/x-privacy-manager/bower.json @@ -1,7 +1,7 @@ { - "name": "@financial-times/x-cookieconsent", + "name": "@financial-times/x-privacy-manager", "description": "", - "main": "dist/cookie-consent.cjs.js", + "main": "dist/privacy-manager.cjs.js", "private": true, "dependencies": { "o-buttons": "^6.0.0", diff --git a/components/x-privacy-manager/docs/ccpa.png b/components/x-privacy-manager/docs/ccpa.png new file mode 100644 index 0000000000000000000000000000000000000000..041bf2cdb02a3a38778a9a24e30af65d526defc7 GIT binary patch literal 86550 zcmeFZg;!k5(l?AVxVuAwI|K`^!Cit6E&&F2cMrjx;O@cQHMj(q;O-9ZNQaboVm@34%9>3fH_#2Ertw_B{sVT2#|L4BRQVAEb#N{Jd~_Z(_t}Admtg z&ho|8-4{<7>oI9ZyxjNgtuq2KmSqPD;yq=?SiA!VhON{*L#5U?6o^BB5%BDjBVe; zjoz7!A*JqOe;3jrrAa6GFz!`i@S9V%mhyMA$~(bw%iT|Amm|}8J1&sfOtIN4VG)_T z#K5sO%cMdnxQYAwp%cu{L2W)Av|XOmRh?~jat8*FIJG;Tb+F3*mutKnH&+tnsVUPu z?l$}#RqIiT^(zJ$CrlIXe*)s4t~j86yI3mZd)aY0@R;9*40i$vX=uNsi2wi)$mjqs z5tv+Kk^6UnUtYf*hnsoT_PJvMOUcu_MQw?*yK(I+%s7s}9Dj}Hw)ZWH>01sP_C%Oc z9AB)xrIdOQa4?rK|1!J-QI8Fo^J+L?)`SEhLjf^PqMQ28d?Y06z8iuv2ZC@Cg0BlA z1xnTsKEsdb9Lk3r5EMX*j)n`xjP55#4%uu-pamNtjIfB@5I|o|G#dcD!ODZE-Gu;x z(}mFNLIJ_^!?YVhw82nq2;c{j1K{H*=<^{DL?yq%n!vMBpbeoR`cvk((IUtMi+@Gd zhO6+G$zd->T!Nkx5fT~v&N2DEn5YHqMnr6qXrCi0K$;b0J|r4woq|xeVaS814^h+; zx#`t}z6Q|j`EX9rL?8%l;D6TDelF{V4XcDm%O`_o8g7*9rS!cRMh#IWK--^QQa#5? z38k1zjph)y6z@aSW6(fui3Qve=1B;uxKv*Ae*ZqsKFU7*HKY^ABk?0{3z|T{YY2sL zJu5E0CNdFLsC55tqX<@<6z>%3G50Z&F^F9O+{glB`RbpG!e98gP+GBDAzQJQ!)yj! zjGt=qc!(9C&HWt*gSU8^zUX4uemq4#}R%-h8=0aKrHV&;jL(?u+P) z^+qftsYHp7p^NYxhLj?P_i0aL0R7qw>`*3hrt(ZBgF zKVM>9N=cOFCQ|Ce6*Z~UXxDtcDBNN|^fwX*D&wkL(%~c+CmbaJ-v@lF`4)>QWf`pU zW7@uev_RRkw6?;b*rD(eZYOXgIRRcBL_a|9L(iWmny8&vNB^RRJ?D2QwxhD3bf+?* z3U8s6h7c_;o0m9=I@4l7RZ3rqTFO=`t5&A2Sb96H>}1TBo+Yv_E+o?_`&M39F`%+t zR-tLF)LL>Y;+@oz%xBQ9)vo*!^JtC|5|S<1B3U9CIbbYbU)XHcWLCM$JbsndTW3+X zVrFlSGr`AqBP^vzd{_$_j6d$@+VOSs)lWVmiP^}c8u zdn_Yc4{*wIFI&3w5_7q7;-zS&8#MYcWiSTeeq?GfH#Tb>!yT_oT{V`jHTk@&HLr8~ zd8$IRTtnxFR(ma1y+SqfkJI{Bvu%rbVBNy*_lgLcuP0W^T+7@pyDqw|R;|moCZ4`e ziMJKERV0kW_o%JJr@Sg8dwi9S$DrrK-^cIltw4khTuurOYnv;*`E0!@L@9WBtjuf7 z40;MpUJlL<<1PY+kD=)WIxWj0jorM|j`xnYKM!0NT=$yJ?9Xlt-QKxBZ%iGP>lu5)qvsuKk zam<@@!XnE;UzQeN8fuH+O>BnQz;m1o`r@YL=6qiVSPnDA$c&pEl8A$+T9W};rU46DW4>uYj5Pd!HjcOz8cqlUan@@`;=e@-s+ip!rcC^ph zl(-GOZAjWxnv^ySQ+vfl#p+;SjB9=Kj}I?KH>0mu9yFzjrCBRZ5pJdDv-bzjGkGUl zx0@U#=8qq7o%#8RTyJli&mHyl0w)aGHhed~(3D+WOWZ}$FgNS-`J}ox??lWb?v}-9 zrdRUnTOT?cT6LUywcmt{MmL=Dt`^yPtnoXJ-pvOuA1`a3j#<3pjP5WAWanVJyTl90HCObQJ$EVx7$BE8)qttfl_O?+> z5C<<}&R5u?e28;1A$GDsXyJB92>aJrF9EvA*>uBiUA|i-1YRl`5XC2u9+&GcH!=tEU2M5>Fp!^tMH>?lt@3l_u?-#W5 z7egWH_vFWKuyO<2*c|2{JWhUk%6r3Zkgtq}D*R7H z5mhm1Y4Bau*um7)*3rVwscx=M70hbhQccTAOJ0u8*v^I-Xkuq%%IszX`qKo0-;EDk zv@vx8lDpY_v32Bg6QKO71|PWm=P?T<`CnC>tOY2w>NzVKQMDLvr-Bol9QA3 zJD8a9sfbJdt2y|a0HuYK6Nrz6#nsi7*_DIY&cU38jhB~~g_WI!ot+6>gUQj|)(Pmw zWa~)vHUR9Zem?>}
      }-(z6Ig~W|9`ZD9#{||Y>QhvcyBq$yK7>z%EB$ypr!$I@be`x}ODu=48 zCoTrg;2&KOj*{>T&ck26*0ujH)%+Qk;pQi)z^Z?Afqa0nt4BG?#-ozwKQ#WQ@B4si zP<~$b!aurzZVYBeJHRENneh)DIl*Z0OiyiC|Dy|GHDGqw#_3rMKmVa4KCoU7EY3gt zqYHgnV0IBT@)J1b|7PmH4Nn5rs}tw{&&(87whmSUIu1sY(*xpav*UvLb;3kZj(B)h z;@#V7i@au)zGJ&=1`mcnHf8fs77M=rx$s7JAiUG*vqWRLzD#n+R$ut`EhGC%29JA$ z!n0=@vtG041dVTu{%BmLO8dR239WVlV{e{REUiYlR$OgG-n$%Ki@&TD5G7oX5tAk8 zEu)ylUlYVo%SvIk<3vw%80;32(NhM(xQVRQZ}kyl4aECxhuCchW+o#O$IsgzL&C7p zU;3hN&An-H{nbaULk=NzVjrz=s<2{N*L}WHKlSl$|94Ac%XvZOb$yf#E`!E#&aB=9 z)Yuhbtwwu0MzM_=y#w0vAk z4sDqm!Y9+#F&HY(6wKm|b}*UPO+G-4g5GR&+N3Jdx!92~EwiKXxl_r>`=hOezU(qs*xQF5wwt#&-u3C>>i0KK z`cp)a{f*7Mq)L|ox3~w=O1Agw%rXS}GI)h4)m`mEJ~AnL7!MG}BK2y35t1#MmaDr+@@u;g5pxSx$}*FMqf;Zio#wF zWQ@Sq6cz+g!oH>c>xVuU6>3VGXF45nkbB$L->Vs+=c{sW?9&|CIEv%S;mZ@6_`ThJ zV{d0<^r_Ur*srVUIhfFo*xv15c@{UF1&BH;ft!SaF6{EztkXsD-~(dxu{4@Ha z(W%%}@T6Ot4}_tDaZ?7!^3YZKjF&p;XGEDw^6Z?H*>TTgy>w1>tfMw>>M5{3!UnY( zh+~c1_J_!;Rj9aYDwBicvRj9o?VM}gHh|i7uhxwVddrRM0{ULlWA09R8IMK#mj#$d|$JZ+qp(Uf7Ww*|JU!AdHyeuOvdk`aE%p?5%2$^xQhaxPGM1N-cIjelc zGY8s9yD?(f@S!UQA{+L4cqmJ1DUT-5nzam$CfkQQbYDtczr~X+H(JRM1O5Yf*$Z*9RE=yV=T@)K(*KFjXt(}7(Rv}0oeam*A9aNDa(9Go6qO-`zs5O47IY@`t#1+-qA0imjo;8Gr9DU4SME zbmJhZ_Ia)2O@+JFyJfvt#VkHCLv$xsI#!`TsTj06Wdb?U*X^)*nVnIkv&s{?@OvKx zascwup|in|YG0c)nsc{5q$Hkbe&`#|2LxMo9t91O9pn(~P_+~c7zhbd*C^Yw%*gIRRO_516>^T6#rxE7Ig{O#ek0qxGObnKd zh9eYLzkQg;ffwQTAa=-s;Hq!LpkX6Kc!7WOy|wZLX9)jJ)A$DN1?dt;7h(DI@g2WiC+{-W$Trti0fN_du_!jTqk;hgQ1mB zcmc@JKZkyU>e+X(XD2>&7S-7hikTR&jN^Nlj=?7zc+Hy@CsVoc$3*Oj`<(f)!InCR z3MVo@vW&&;apv@GBD3mWJHr@ySPdD!<$A3&KL0rrrUaH6YHT%BM!l<(RkY z;)$KU?N`-Qv~ADqFBqndq9FEbBEF^QVU9>7o8u_=NiVo0{h#0H=O=7hwY`DlQG~Em0_*P? zG-MgFUm4l!jdL!LHCm6FF8+3$*&zM#8Hl@4`aaPFMMmXbhzhe|ay6!V=Ru7@tSEUE z)bu`vhVvdiJRfe$bPSWCKvzFlnhb9kP3 zXeekUMM073i>3>DZel)+XNNHHeTwRGOUF>~7jw&0sx-30ogy2y&PPbjIWHWzv2HbO z-U%fu$l(&n;4H>EukOL#v{En2hfiLA&u0y?R$-2&HzWamR>2jz8Cx(gyt|+|#U;%z zD0knS!fm}HL8cnR!+p{P1+}emSNAyTLdDOa1~jRE6cfHQR}fz7rc^KDvELrDx8`zT zTPV56X5HkdrIqcci>gK?82+Llo-)j_OAsizC;+4P@iL!eJDlnnhx)LZOhEpI*|hkZ zh_AT|$(mcK3pWFjkn!Ik#k~*|+jIr$+#ud=O`~vg%m#t3yYM*;w>E332BQ?Tr*oXG z(O&SA6g-e=ngBM#EtHifaY$$6{VnVdto4`Fx_x6!H2~`s9t%|yaB*rn(Yl8!=`32#Y-6q!XSj&ff`dc9|+8yTxbbAnr{u3%)cmMAP5)YT?;RqezC7kt8LCZXrw z4SR!$TIeikT&1!+sfuSD+eKpzqZo3k*|sFbK6Ng(6E{Qv#cgD$6Eo%PJHjACe&uXIoR#ZQM=cPSvYIm7$U*1m%m3S^r|Rdv*V4$GQ^G`{Zn{FraacMSwSWOQx0q{%XizZqGu67IEw;zF=yM_?e z{_8QIPeBTXS_y_hdy@mkv55*+y|u4n>qlPA26>8t8B7FHt@VDY+1*sD3gx{67F7PJ zA=QW>CtC#!*D~{rwjxnuG0UUmtA=m0=a2$6s$@%zq1%Iw_J*)9?=r93k@*6{Sq(`k zn(jr>X>0JH-Z~#_%dn4~b`1)6hzl0rZm13_xm)>6vutaV!t8Z zci}$8|IBJ7*CSp20dk%DKpK0J1VXoKtB9wO;I-vBB(l$$l#b(Xj>;kz@uE=rfgC?O zHR&x2)DMGS6bgHVkflJCpA2R*zBfCYj}KknP|BPiU;EIJB&s?idGmX*Os%7S1+5r< z`fg^^UA)nl^&DBU^$7p+Upp48KfX0D6m+2u#NR$btj!O{v%g-Qm9TbSA_j*k@T9<2wvF^ex{eT5g{m%8*L%veIKa&F(RyEd z`}{Kaxivol`bNU|NdtT81Rt3{aOza!{=)IEusxRpswU5I=lIa9a1PcO@sVb>U7?@* z=@p|~O0x4R5^wcxtoT0bHJ}EctvXo;WHDO-gWN<)Pyt~kXO0?g+{8k_)zWHuxQ7yy zpCnH>qB@`vZYK{U3n#Eab?(0C-*VY0eUkL9yAHfTrg;@HoTux{tZaWBx_!I}c<4Qr zg(8*eXFDEf*DQ#@`yoG?oQy6EYI{mEMFMEZO8DDG^3;qM;cFog&N;j8sG0ixfLre^ z)#yx3S0W?b#>7Qu=y9{2??{&lh*KxTxLB!F$V%F~CU7m|Q83~7_?^St<<8z-pH`-v z)}^>$?XZ3I3I+jlvAqz6>vTNsJ4v8_5yn^H`c^jij=!T`YI1aACMk|RCe)dZ!~M@% zYEExYGv>Y8bf+urM3St^>$V8-)p%$i&ih-E)|GNZjVY|~7bZ#eme`G#2P9x8T>>p| zv1y96%<=~sX)+d_eoI|!P`u3{wTOdyX(41=IdN`@WO|3uU`1;+!pzzrrMyR z6bCj3f%;})!s=4S$y+8v-*LUEDyLGtcJ|GCI|GFEn1gqWdJ*L1`5qUrveqVU4HB^m zHgwsb*512@C=Fe8eJ|f*93YOLA6eWs=1wY~r+>P-cp^psHB8ICoFwYO@)m^mmheRH`q9v2%oKv6?{eVwU)ezPU+7`>Js7$s)!!G~#90({W$+%`vy$sf zk~|UAj5j?Q?v*qR*vIR|uS&z3K10(+Rz36Ux*pm&x?x~Y zr`sYC^VI*|u%uBnbiAw}jt;W6vyR>^fBnIPkl-hq$;g%_)&XtHYq3#LyOtpM@& z!e87}+{eGHZ)3tR9wOb1Cib9UL3Jt=iJ|d@57~!fkwlLmdNpZgDulF>kxeO?Pva04n$tr*hbv;4%^RaW?{kAccC*`y;8OIv+ zzNhWh%H??|esM#xh%CFqYfk$iGll(qgz{@!^ZtCrM-|m*)m4W*uS9wz?)EThtzi&j zvyfVuRRQagOQQZbJ&vA~PMLZ4Yd<7la}KwfXa38q3aNj#EO9(CBtob=zTWY4zV!Pt z67YH%jNNXs_ZC{@v5)#FNR8sUBo-PwJ%m;W*XmBm=pALQ^{LTzeUrX7jfHEH_u?~j z)CAU67hE0)X7e?=2Uc&Bs#n6qa&SMu+{8ka8TzlJh`%JcHj-49UvO;HRy}iO7S5Bs zA&OE|hljZtQ_sgQ180i^FA2OjgW2m1OfNZv8t zpo(e+*S?>#ne^GDEX>w69V@O!_i*S)lNTJoI0U92(m_FdqRJ-D z^eYgXq~)!YQn+%IvL7Y!_^D)D9eDo=kO0_FY&{h}kr@G;_W*jq5G(#dqRkq#Trq3& zQ7lF|)d6I@bsK4`q-cYQF0Clyl|eQXuTN9=tHgDms+%@G3LJVWm;t@qlVmJr+bOgT zB*UOljI3!=H}N;5jYEQYAt%$F@{F{u#p?1}?7xcU#A@GRXP;XjDX#KejHjs>G15m2 z*(1D{A-_8;;gd)-$e0X&!1SIEKd1OD`f$Yu8~ODvu7D&DDGOsdEQ9VZ-HV8mF9D=9 z;2~*`$@|(q=aY9Ae6i>xt$&U0|C3t2S-NhN*oLb&S>xnR{)mH)4An7naq!Jujocv2 zOsBo>N!=6sxM%;YI@VISiusXhVU6|%-L#vu;12#KE|j!*?-1`)*bsm_cJEhBPBe`C zvCe$J?T}O>C;CW3i3TVT$PkSqLYv0vK|7w!)qP#h34k#u*OmLx$8B4V&w6~pwgntx zaUnjkJ+W*bTcWeoy^C*-dN{m8z-q~6y^9*3>ILUmr}IXRz>Y+y`|dDw6UxDFr{6WC z8l9+WIw_lBUfPkp$dV`r*6vy`LFlZDflp4ZPd+GHk9^GAgbEsB+82G?jv1f4ix6ZI z(9Gr81pzEcJMELO2Lp+mlAzBIziM7$|xn15DD>U z*=u{^xY*e6LWP@v=M;OBOtdi&QXdEL07&5&qt$1d`aO1!R7mo4i1G>yWwv1R^_J{{^uJwwnyOl|KRTN)lUV)O;1bsVU+@@I zupeej&m$BJJ-k9FSV6=XT{TB7hCu)jiHXsFz)=8SFieH$JH{&|*%jP{xBkUJZyRd; zei)odT;}Ki{%2jXKXu76c=063k)hDY1asA`J3*fMVqPqf;r}wjzjDV$=oM9bTA2#6B94)9~=W>!H{V8V2b0vgT8+uf&R39V3Kl9 zTvOOTIePukw%|a<-`o1X)5RhGV2e=EeG(@BVIVMn{|zw6s(jA2OZ*R*nH-TEOv1K! z$4vPTv=W943`qvWvhDplcKtUBtPBArz5fG@dPVWV|3krHP=83UgyH|s zWPe%i|C#JR=i~qPDho^MnQHgEJDA>Kxc)t?DhqQ#y6NQccwG_|=;mUN0S~-b*AdN9 zHDk)@RLr=hJ)E;%upEw8PzZ2?_i>NR^rlL~tMR$eiLeSBDGuLp;(o$;FZr3MCA z{LZ`Oeb@6R%Q4c$E;j`yU+)9q(e{s`(@WMTr|PXI1P7mUwf$%mvpzA#zPPW{@8j(> zc^EzZJlzYOq1Fkb8fyiKR5%Gmu+}FV4~fAD!cUj600xfXO9#1(0>#q zM&M2c;b-U!l*ga7>f95XAZ=&)V+;Toxs#W2N%+~~YvgB@hw0X^Q25?YaIVJSm z6rWV3O@tv)z+pb&Io2xv3DOu;I3>fw=l0vxYNdq($Fm4dy?iw#BB4|qNl^RLYVGNc zY_gsgsl=^c`%<9igWdY-`(k907k{vt?hU}WE&DT>@5_k{G#>u_k9BML#3L>Y(S*Gy z$}OV}X1xc3%uWzSV+^Oy2qT6MN`VjCdjYrmuWKd0!pvB7xA0lMLjKI^6b1tp_V3;A z_t~^Nfq6%33rKR)CoMO5&1B34-BdL9X;J7wRWfpCYwhOg1KERbaVIU_gDh&fDmKg2 z8Y~NTi{fIE-og{_Wvy`D$p}fUOTGnlS-zvirVqEj`Sn;8T_tx$Cg34{(sGw4@Z_6^ zJ)F&ff|Z3vCgj6-6zt&NRuRi>8Z4}pr;L=1O}qBn7}@h?=>tWA9*1De*Qc`v+advY zRFBNN*FY&K_D;7%x`t4|v$VZb}W40mo77E?uJ6?v{a%9y7SH5theyVJ5d@ zd41yK=NejenQnNBGut44F>>3DZxM?LT(+fPc6|*cDa1KPIbHn<2J!NyQUPeU@X6$FO`| zI%g+I1$UbZ26z@ju5L}swQBaKw$UYVu;kn@a2*T|md`qhz$mp!CuydaJmtuMr=SEP z;ba*Sj%QxAl=GtRd3AjjW9r95l_HO+Jf3dk>p!^HjLYi&6%Lt7OMS`k@}rw-!_pjs zDmV;6@aE^Y>}#S^K_{7R^mH>L%DdFQ>R)h>Uu?3K$gkwyJ)*~1+51v%=TE+^Txk)pP1DOYEN|Yy{nKVE-bY8v$ z>?t#Uz1;DUXRmc*wceC+y1XWMbl8zf0LOQlDd6nAz1}vZ<+pcgtOleH$ej`fEbl7h z46hSF*AELs2REXyv@XY5^`~WaHeiJKr4HBYU2q1E{gzU*O8@F*?~{9E@)Y;FP2vPY zB7;0QW~XfWfI`TtFPP5b&Fp=1VwT4B>aG2D(+D%}x*K(HP&d(;dloE>--Ed(M*d~K zm{h$?O@E+HhG?hB?SP%>`5r3(=e>SglUl2`snv9KjLxsGyHcZKD|-V9S#)}ONz5sm zGM>B7{rY=HN&ds*YtW$OsS<~y7`qClB}CfqNA6K0QBJ=7Y0F9Um01)AKigwi-2Nl)%g8x6DKRl%WWvF&*2 zwCG~B1Uwyx?CFT-yUzkfGuxi5P12(63&!^fRXiK_o5{;pis-2vy%w?6O*%DpLL>9dO zpH)x4SBxItO5$GyhJj8Z)k_ukP9%CA4(has)2a)%OnNW+aI6nk*9TaS;(gMYhp^!c zx;vQEa-SB0p76`X(EU7IZ4{dwJLsRN83S@q1>lt=rmA!zeNf~n%Nx5WUSusx^dhO% znuPTUP!IYeiIRV2vJs%InP?0uV@|Xp`P4Wh#RkKedtTc|6fs>^aEAx&LC%%y3Tu zlpve$q#+TwrLp~wmZKyDen$$ zTAx&&t@5Oy|H{(H$gLZg?z*oKeK^mm-XhG#30(TJ^lpjNxG4sm%Nl(2Oq5{!&}7af z%y^FF9Mx@kd3W^3jPvTWJ+;r#or7<@A}^uQ2ppss zs5gjxO9*x`sR51m2YY(cmwq3V=}wLZ0&2=1+tr06@#~%s=gW;gE0%B5sv_1%>Qs~| zk(M~m%sfodGr3*xR7@4aU#1JX#_0xjf9#LMLyLV+5v@!nmj^y}g1&N%1c229V?w`M ztmeAA&RNF6tgQ;)S4xx5`h49XF3kpjKjL#a&reL(-=V|2PU^4+MV zBQ64@@rU*sNsM9g0E#(;$jhGh41GrT4agh@O^~)YfhbYUrHXWH-G((VFAR(t^g_=@ z=yofY(CT1O{M)oyZ|^%_}41^r(7*&k=%cNeS2hp06f3ibp61`y?BG6~i5H3VIM5@SZfAgsXFfvr8%4=OR_V@P0Nl0sn8A=4Vr_Ldnb3CQcY36ya~C9awD3##AU~r8fVr+ z4^@a6RKLsks0Fw#0lb)}qX#T_)pRE=C}4Ev(~<=p5OLd$z;@qE??N2}u)$>Fh$7<( zv$|?6c6}8-hi?DI#L%vH?d-0XcllApQ1qq7Z518LqBTjLR(h*%R=;T#C;~?zkZ>`K zML|1XLQ%oIzm48Tj<1GyK>V_;Sh}2#a)OI%aP6MoY&h&dK;~P1s9yg`grYtq>U$~9 z=u~6rFL5u)ee+Z~-upHTLT_+ILMB=$d!HyIg!mmr6gS9-b!XY-`PgCZ z2F|Xvrce_hB10!2ZaJs9GDrTqeXx?9q@f9$dFMh7Y5_QnPRI)^#n#2ZHRYTiE;%WG zgq?Ow%fpbw2&xwqwY)v9LwG;%tFL9_q)AeH-*q1vefqmzHrtt))y$9Bl&f0hHC^7F z4Crp$-^030&>r`L$@axGHXoAE;q-Ae9AgNr!D~1Rtr9~j8!PjXI6-57!6vO;mWBU~ zznh7~QD_KJO7YJcHtC`E&5}(TbKQ7|w`ktrjmGy=>fD>`KgR$A@ZT_r3 zFMO*&NCORUUq5$pdxliIT%oKHSr%wXMe_%;TotnEb=;BoTL5|aXr8%5SXXs0IK}q8 z6kwP>awg>jhWaLJ!)b-jMo2icPJ0jI!K*#+_Pd zHTW(ZtAw`Ffb~n*N6Tv^`qz1)%ZCD4&3D@>n)R^w!#6!vDNjE%3kH^Pra){yedGZe zS{PiO4S2Y$?=$3Z(~OTl(?7TQ{(9HsuRgAC9iXX|7I}6Qx4Z7*@6(szN8p5|Qe=!kfx1+4@>VlV z5c^mLPsA2tq~gFe%WxpxhA-;T6iRoWXq-}&*$2zHpba$9<(yY^+iLFfZAC%HIp~Lp zdqXw+$dOrls(d48TzFPlED53l#bgb^z(n*X1d4L)Iiw!yM?(B*E1Xf;Mn1+BzK6(? ztfXHz_ZZM6^-2yoibiJ}E))s0WvPf~oHR~ni`rW9KDo-GT0D%8^aBx?qHG})tUv|! zn7vdisZn022R~pCM8Ts1A<(9ef2Pq>N*(zU=1fdVY*(tRrVumGrKwPf+WUF9XjaDp zxQ!%#V#XG`JD#c9@Ey_oIg`V*yja%3LlfY9)^JEmV7ChOr96Jl&98sB)F2pj6uASB}>5(D2MLa8d%?*&^@)p^Z2 zb_U|H#|z%?P3)s+SwvyEdWnpP}MJ|AH{x(hSb)jS`w>JSlPr(($JPIjMR^!C{m_~UEK zFmg_kr-;N;BQ$e~LM|X%lxG68K|d@%e4fsqRnbDP=3xksg@@}M6cvMmiia;*m*e5{ zzRf_+JDVD!)Gfgl350>F3yh8<*9zI-;#O*Ub`zb%7Gs6-|J(-!I0;i-!U;NOvwoEl zUG~*xpBiD`khvG0V-~P>b1TvG(;74Jr%ubQ)j(jri&}yCIRa83|LZ$ z%m8|zAkC12d@jn+;V3}A;vyp3DI?afMK`G_)VfBDf1sCEA97poMjfi0PWXd@1gr*w zZ=h#=7cCyjUXQV$GM7{}$nBVKw6vki&lm5ODd^P7x>AFcAPezvaV5}Gu>A>MFVoLv zD-6qHr!==-!zLBPAmB=1dvJyVkqlwG0r|H*U|)6|yG$r37_g%h`njv%7zNl38DCNS z6@4+f(rn*)WUH)N!T`Se1BTPnPa8Vo2UQkdwnW&PhrIH4=(yy^*&oSnm2ivfBY7=T zsvEYhW$L+cZD^(k>3e#Zuwlwb0P`R%E48((t%`w28siGDk}Xb6*27kqUITGj+nmMy zU&AfOw1I?nAcK%ZEYUDNh(RryqK3LdJajb?4aa(~gY&^<`-*R)n0$&k@>5AH=0D#V ze19_^PndFE`d;PuAwf(AB-t}^cy`2-5bStw%)M7!>~htHnD=(*n5&u{n0w7&pGI4r zr&P}$PwsEM+SoIN#ov~?cU_yVcjUd0{y|6a=sw(chp7z~ zvhF$R0RK!q{c}YbeH+bHo~pLhPbEu2->G?YCBtYqpl!E6xdizbi%#RoR~gA;X?P(7 zk=HmStvj+kGB&B{+xvb z|L=h-;tp1{vOve6D75J3F~ByYXONR<*LqFRw>TG=X z!`PLA$-*A1lOLG%bOy`UQ*CHi5Y|x;f;M1+&oW940PlN&}`93z9t!d z#i5IwxC#IckVXoBQ(a0C-^>koOM##cYyYsAaCTN=r0}pbhau+xj`jCi*$(+`L-l!soq;EJ0I$#V>17kx{9BhQaVDfNL@0hDq@I9jSu4=Z2Y3 z8VdE&7AHVp2B$}g6IHRwcSiM!g;~0^^)renU+@bEm!CYEH2ka2P$r;7UT2YzTZK~K za-To-T0RS>^P0@U@Y-&Q@&ONtlRYzNOqFi>%pspM9otB(_su~Efk-9~?C)2CJdJ2X-J4 zjc=?M_CQ0><=JEt>C_dQLq!_f8kPCwpePG|HOS0?4#d!PFltqL8rDOw)}=t&r|AUE4nxhtmk_SUG-m!D|kSm0JX9-U6enWSu5ZxaOvCJBKo&@rd5 z-8KkNh+JJPm__y7&bH}1Um#x#t3`P*>dEdKVhOIo$M>x}p9fBKgg>D{;lUB&dwqEf z>?{|Y>;m=5MWa0P6hjg%egC{l$@uOSJS`UQf}+!T%->q7-}#Nz*RCb?(iyZ~Z>TJ` zzp?T;{Z4uIBgTJc0X@~xg@NB=gRIe#E&j`R5OLWXh~n>TR?B0LT4W2UdL8KdDqx!Y z!%I``OOn(|YR@XV=Pc+O*%C2Ss?jtt76#-w$vc30Sf{Yot`apMGv&ANpHtIjUS2}7 zB=h73_^(=<{)Rlk2m!c3lCw64;KN@HG*5>P2+{1Ecj*nU!k`!3R-eEqYv6g@8Po;* zHcZg?o(0|xpI@Lnl*sG+D486zvQPddr_ZDTD=FOFfh12QQIT#B(*jYz2goZjg)adg zh-thw#b(yNH5$v4gh6u8(Ql#Se+=;CP>2p#rcrPZH5l}PVyLzL*y^*%DW$VhBy{JZ zdt@)r+>lm=$D$)403UPysH5FY8mi!2o4$~-wRF>*(IbE_lZ|2|w~^1{<%i$%t@d1N zb03YAhpy3iZ_NRjH*xi#eP(fC+n9_q0hR{>UX)^h4b>RxS2U~u%JvUEp@z|kl$Z-a zr6c<38*V18ArYl%Lk8$w+~Y{_pVYWHY@8F0#oqfhUzVb0euA}s{*i81PD$hrw^4yT zw>Flpad72$h7oq;oiIcIed-ixu82_-Ii9-~xRxB;Cm!C5D$E*ej71uQr_UHu6q1Ml z3QI-Ccct# zFK%faKmtd8-qj(bFyFLvwLFDv+c^7q1Y)2qilTFOZJTj9BA5e%5rffs9`{gi&SoSY zXTa$YSuwP;`lVwm-K@~l^FGADdnmpmMDbkSpAg72oF!yJ%nxMOXrnV-&i)VP4y*-z z@IQP}wT2{lZ&2H8hlUt+2aNHolvumbV?}u<37$TSS{&j(>16X7!HYv zvIRN(j2q@{f(@m-5*t4E@DhmQGW8wEfqgP^DojsS$F<&;Ii|4}=WKIwRnagt#aD6Y zYyqa%9iJ(>a?Z9=I*v5U%H0UKJ3hw|FN3Q0j(UfBFte@TVSL z5+761pmcgqFzE{^bP7j2_S0Xm3Wcu20HELJt&hD2EcGk7~Kg z`~M`o>b3CKM6>3)yhFM**It$o`d0xC(46+@O~VhWXPHq;P-Wq=5{hI4^r`N0IP+ z0; zP{KP;;XYOxuOAjMnS{0Z2wuay-?`t4QR&vWj5EIMEDOqOs>^{oF2Q#E+JhL( zy2r>j!87c}_Ld)*d4aM%nKj(>uY#xgFd$kxBQ5 z5ke)&BrnZe(C`)I!1us|^n|J7L3Q}bgx9`3;HiQOx$Svd=%CQHlZXwO%R^aWzT|?% z*5+FC@9_S=;@h9ml~arQ1fHdnkX+dQCmQ|dt8+Lob?*|30sdzQq3KKc=wgbz!l1KP3N}>it_hG^X*896IIvE&(wc zHZShquS<1-F-V8+S#Il0q5i#GTJ1EL=&l}xJyn8gI)#uICu)SAtnQaSH~HOk*ei)ek{_OESnc5bsrMVn$8^D^zcO5Jx)#K^~uENIS;cTZ@u@d zNAdm(1{#HT$14nGa*T=oV0zRER-&M;*~sw5<&exKLWtwx=+wb;Sj_8|CBfUj$R((A zLgZNn%xeoo65h6baPR@Bq(V~1JTM*w*G;)k0Rr-dj>}utPPM=sp}-)Ajh^l3JA)M# zPLmTekiUFK-wzS6^HQ;1$M+8}POVg|7Pu?veHA7*P8W0)Ua*=ECYui-5ebk-XD1Qz zp*n-Ufq!s6VEgJ2bCCoVQy@Pt^4_xO_b`k+u5~gYz{40ZCmJ>7rTT^H!MPf+##6=u zswD7q`%7wj*5q@1KKyVsndtO*fjj;EMtFnAnr$)?ui(5re8c#Z&fCg=<8P`9{pVk- z_A%6*^{H4b2uyOwo(_T1XgR@;*RMy-w&M7n6WE}(*ERz@mf$c%tnlxH9rs{D{DDuZ zM_0*+FBVf%_l-(L&`Rh}2 zrDn;qx2qZ$ol*(+zFpvQ*@NPQ4KK)x<+AvV((TuJBuaQ3?jbY{UTGYNd|yjOE~+sQ zq@J|*afBU%`2aAsMBc&UAVMCsh3H)?aPE&CJnA^;_A6_D^35dQdl;K+tsc$aL9Z?L zpQL%%6Ox=s#b$}qCy7W)JbI-4#s`sBvcn)J@2maDhwSc9uIEA0H7734CA^4J4NK@7 zkRsefpgG3Qt0@}OtWhmNIV`t*N_~f)h|EpAV7&Qh!@oBN?9fZ>%3^zpz1|En4}7?7nr}9e;XS2<+Ovx44eVs|Q5AK&s zj_&#WETllfA>YBAjiEtlRd>u~FLz_3UX=a2ai)UnxZLfcl}_^cel`v=v#TiZnLcA~ zU4KO_JWU*?6s$JSHmKWD0d;;D1sqYXQ!8YKj$e+x=%D`Eaag;>)bDjpUjG=A9^rpmBY_o%l(!VZg&QduRCP8@4wjT4&{8ebSXEM;XG;R{@gxG;Wjw8QQLFL z4hBm~!qTr30@wLq=UHw0v3Zx{**owVOZFRC&>(c?RMg!5$5jFYKXPg=-&T_E%nvOn z$hg&B;+RPGpngi}a_JH70dvMdeaDlXj9Gi9!(e3^w3u?V)+;sh;N{KV`*C=7 zdYuE+O5hW7s$Q(kmjLM&>03K(u;O3a*&yO{&T7BWF4xk3U*34=OFsBj$MaC37Izfz zgmlCUrntWYz#2-vf&Q`zI8|WI+jgjRb`-hRK>#2i}iaRZv|F36YJK}8-dFiVy}E z#S?H_6lT9o=S@6L?i>2c(Gl)-Hkjx?T-ElL%Z4?4(T`=$86Tbj(_C}(okpM2wCgEw zrb09RB#BMp4bGe6EqybMS`CiR;g9$$n5Q6ST1}gc_D!R67XRsxdT^RJZA%dF+AZzP?`~LZ+=jwT~%na&A ztR>~_7EQ(>TAv^$dI#i7s!v2ICR(Po#t(JskOXocH zXzMZ-TvwzAKB5s)GJBS*b<1cy)x!myEbE-5A=q-UTufTOM(p>MfvYDb^?eck4#S30 zphUAb#{-Lfg46Ge^*`ay#7EP}XDMjb=oQtA7Zh8arfp0wgLC0qzmtnwgG!%O9qTRp zo_|@^oPLOie`eVW$#uJ4%B#77_tuJYB*s)))KnTdnk!X0!0eZMPorjUS3f3I^gf1J zj>`92zFuFk1eZd2k$i8n zB8A*$In17_JwD#QhhaaD5i z6l3xVm0}9>v80T@Yk_1}qO`+XoF=PgqYRwa-TbY|HL?*2;Rxs=Na5g0iVM}YO3g+F z`<07Ex4?!`WGLKGjYbJXQwZFp3&kv!iX96o4}MTqv$%$inlA}2p>mZ9?Y2j4zI|G#iK+1 z7JaUmk>sMJqEFssn#k2LR!~K&l$Y2agF{aTGi7o~kh#wT8xqsevr>_4=h<}Dd<-fF zc43`Y~u#|#cMYF8iGGfejLIjdcu8Vt}-yHi0Rh;1V+wTaYEP#!T0azv~6i4 z6c)`)6Bk?a1qzqd27PqHRyjg7iajBdRox_#wWldYEz`qv64kb|eXydp(x<|(vuk2m zX-KX2Ai~gY>C;rHSG;W34P3vEN~4OL55+Jm%$FZ%1=u_Ds2W$D#Q!xPlz9_Asez{g zoPw0#*}ciN&d_$Ny!1e}JaI8gsz&D@1ETofH0YLO5iM-8jF#B~gSv78$A_s__m1Yv zD87@oyY79vA{{17amEj&g|1QiA*1N=0((q9J4_VE2XO_8&-09F=L$JpJ>pCgXyzM! z%9bScjr*`%q}xkApnn(t1!oWaDopj93CFJwd=ZKfU3ph_DPVp$=Ez-c;q(;7?q2ir ziI_YajNn5w0$L1eCJ=V!?@pHqu9!p7=!lfE1*x&+W#-DOZvw1&SQ1m`p5N{-FqfGH zVOb5gMDD|j89`(ft1*l9`w)+ZX@=!uIY%kdbfWbhj6SCeCoVl9z9ke0G=DgzL0_}L z@AIQC-YB0O3#q<56p614eT2Mp<|=*+uul4xG)ih^}SAdUFQb3tI8QcwAplCeeBF z|HTZ=OGDftQt_<3jKd(svNkHz0Nx+$KfemLAAl2Ow5)MUWzn$RQoBgvW$l141}&QR zx&q(^cFqHCeZ9Ii!@1Mm;7p2?m6oW{Nx`oO3#?=Y88(x)a8U@G+5DFI(J$r7imVyr zPvm5M#z}a5Z3!sJQYjw@Ahlqy4!y;U`UX`snF7uUM?wij=AnuPBm^|X5#l;;PfB$b z7=A^epRtm2JL=_mAd(~AXlihDWpS8iS1Ex(uZ+bWZa+$@PbZx}9jA3!9s}vt0;W!& z)sAhb%@hV{ic(jWfVw*O=*b_eR)j-&aeW=2RwEMnC0AU8N04&Nw^;)Z2leEgkFVxMUWMJlK>X~JnMyL^ zWj`nyn82OQW!*F#t&}vtQsoF1^<1>uuj|molVV_zp(LB5cZ&&$uwe3knMRCt6zbb> z+L7OJEZsN?>Bo{5FC2Q#+lK4BPdL|1S zH!fStERM0Brce0W+ro~2vj^NOTm8cLFvk1fDdEQ+kQVjnijBxUY~U*&*5+OkVEV$cq$LBLaFVyPC{h@&A04!#og}T%zG<)^-dplLiu}89>{de86=LT3Cjz#K9Z-9%Ct2Nq-r7gJJuD zbJfGOa!s#Cl{Ja1vHs3vbbnXCu#R4pHIE6PSC$xPULRi$t?dzWQzH}P$Kr^f9+;th zKst|Y%Qoz(Oa3V+S0P>5M_e?YEvr+!Xq2?EMdOUEcOsit_Vz9I&@f4ZyEpp4SAEo3x@wQjH2_}^GmjUt7=2Eih{^j856 zH*XQm37PC)!io`&(aRL$z8MM_$S46%mgih_7YNgrH*HgCMUchbWuu5d5h+9eD`H{{ z6kHlR^mKdHDYCUd&KM`?Swsi)X@~*wi`waQiN=s9$d7yovjUuW`GHC~iJ7-S1uPio zidE@!`2FYm?wbtol^;Wqtn3NqYgn515j^CXw8*rGM8VBn%nA(^M8jQuC-0p|U!J-b zT_8haAe!8zf@y3kobCopP%L0Dg_}&kLP!z;FjL&LNM?!>Y~wdnjWR%2)6ob^X26<$ z{j6ZWqd9fqyZ@K?up~&KEb5d083Hp2p7Av405x(94CfgcLUUULH!=)|$m|+ZlO1}z zk$rz~3g>5&@)Yb1vKI(t>@)ejkA-QKn)`Zk zzd^fW|7KitGotEhuvN{&{M4#zluW4fs2SANE^opw5{yiTpux$cu|FAMC4736MB^5i znae*M;Q}qu8`exPP*_S!n9&@gpXrg_go`6a1B#-8LL!ukx^s zR6s)*;4Hdo9f~QQ8gccmOqABd+Ov!=1Mya?&W!Z=oX!!NtycTEK%!b<6#crvNuM$H zz5#~#;Vvy+od=YP9dB$~Z;b+^NhdP-k{LTx5chqI+v>8$PDfEBsefwP^|=9&P4{co zz8KPdF~IyAosT501yvNdwPQ`6l8RwlmZ+E~L`&KqrlmrjNP=LW^jYh(AN9%ZmlskT zr)l%el05}oUv`$$N3Ok!YLoQ{RZNyeElqL=!H>_7tE*y;SS)0L7yIU$FNJg}g_dFY z$~q*uFq;T0FXl{N6sdRgLcnHnzSk5TqWdC0isoUqlR2A=UAhv#$Sj?(Dhzr9&p~G z$KS!y|J+F*9ZLpJ+@611iqinHqNcW>Z*=y_H3Ba>R5EEiM%bT-A*cZi8g2vwaEw0D z7wf}b)+wMRX~64QG$P(# ztK?ynWHF<5=zWM%Gg)2ArDg@0e(I=3MNPn?4BFlx`7ss|R$gT& zBXLKtH;!e|&ntkjs!rB*^nwM-2%YV(2s91MBKBVCJ(+d!iMSvvY@|K(F;&McbvqaS ze9#4@`CT+o7IPH&+d_Kdz_K+<{n43^aK9B=k#A{$BdGLb&yx`)`!TE59-Nvfo1AcQ zxTW>S24y{rpo)CYT>vZ-9EX7CPPAy;T(tTftzzSwRR@;4mhs1ql!zl^>OS#m}0PAUEHV*8X~HGJ9a`l1$AcrhXjO##5#c z?nJ6K{=nS7dI%Fo%7nRr0OGeJ3h%_1v3|(IjU~ISg$eFs>Lt^IGYS~$$=M_Bm;)Pa zNfDHy&=8)mN>}1Kbye~he-)gZy=xV4AGHxajuO?3#=LxT%E@H+4H|@yN5?Z+h32tx z%Rz`C8jTRb1%Igf-oMEapsF=nS8qsrVjN^^0@mO4OD<^(s{&&_jF8=T0=Hms%R;9F zDf3=Dk5ix31M4twak9W`n4|0r3K|`p)@I){4JMBgl}l$EpPJuC#+RIwPQ=R)$4-7L zAWPu3Kxxy*=n(c-A$J`e$@;p?Q)kwa@(t4>np+e4J*)wqrUL2suoM3|V5WX4?kRi_ zc8$aEP+v`8R&jBz_gwFnY|-cCg(0RF-GR&)eugnB&N)0?C-sYF{i@kdol9|i0#&E~ zOi5FOn`0^&X82>UUjEO2hD+&XSxbOX{cS#A zlhCa!3y{iI*}hscZlx*E0EBbi9!a3>CNf&JVE#0?Tsk+eV8NW{p^nl~akBs;ZTSMTLpytQQ2hgrx21m6lv(4lF#N(W;A#x= zL1E1ZcjpNjvANoUFZg;8{a6y`K?&FMzma#3k<8@|`+uxJ&>;NzkcOi5C*aYXXDA+f zcFpJmbX*?0TnA%~&QsW`=?_|{W8&wM`Y6Juh7>~hs*vexrCdIxwiWwi`(S?|x`bM@ zup%Dsa?BTt@h9a}#BmrH>65F=)a+Rwx$LFz0Hk9|?)`Xl+Fox7-fF}MoxYdN=|s~8 zM__lk549&bh(Upl^dAUa^DQtEv!m_|!VJ`F4v{62dke^->IyK|#BCh>Dxl~S{KWFe zJPgfLPiD9>#G)V|e^S?yC0F&>S>N-1l6&-Z{AcwB{AsNvqSQU;BeE-D)opr=4_bg> zjYRISC({1JvuQ-0ny{B6$j~Nq5_t(T@`X-I!dW@E)Ql_bNzFpQ?$6^on78w_Zf+kN z{r!%O;~U3jxz#jvTo7C}bA((Ar!`%_0qq{P1s)jYvqd{xo40r<4V4LOB7q~+UXVl- zum;e6k(W&v%j^scsMSG#LL_9!$ojWUPj>JfB){sC-4dDDhySTsg$8CBx-g~y<7!P8 zY5<4YJqBf>kx)L1e)XE?-*z^f(y{9rw!@sr@DOTpmk2p6X5HFbDx3V7TcaJHB}wi} z3SRZ;P4Rlr#K?5C;*ErYn&F`dV&UMJWHPAhMXaWuw#)YWFfQ9!eviR#q0!LFBi;W+ zW)g^oLpmMBbEe{wFCq(78POX4=!+^yZY~_LNc%LVy)(19)P@l36x#-t3YS^Jhn zx!_AAhQvOGJW{)5rO@^%UoSF2Eq@##1edlC8%ubK?4%Q7^O{Z4!n@4#zM2`&pNX57 z0Sks47M@hp)q(27Hj^s;#C}@PtcYzu7(?1DxQ^D&DbImZg>@di=^VA^cGO(77#76O zN@4@R$Sum{y@oKyWA%$giN0mgW%!g2yNDj0BZFocM!7u30uV~j{L~pwFssQv`A&^l z2z*sV6_-Xu3j(uegJH4YF-8AKuSoF1CYLaY`K(>LEx6|#sqfH#_J>PIN%%@StOQimfA}h!;IvqtpXI74(49OYU&4PM%wd1(BONUJOjL{~m@- z5e62eJ_92%)54)Ec9(E*g|2mVUp zD{`|Xq1T&orl3X0YpkOnmcSmiW4Ze=-eZ(3W3^r27Fx-QV2iwGfsXgU;tw}w>(^*6 zIyG6c;Sgd8znI>lFR``Rc8U?=gKXb(_GreTXC^jCTH3O>%^xQxR3&TpR?UElJm53a zpr-V&)Es~uj+l4$H7hWWoN~870|8yA`P!wz z)^M2N(LlC9uk}cd!TuSF=Un^@IjAh|eg{^@o8k9`cpXszn)nRjkbs_UKU#VFZ279< zO)Bin>z`1O6Kw(a$Lyc9?YH58IE29tx0~U)6z5g_%-Ep9QK59(6UQ0<{hJ109P$sn z_G?c?(A|*p>UrXD&qse1=V;3tlG6RbI84y#69+h8Cq_gAnMU1QuGG!(YBEO>pP)!_ z+X_>tH+((u*Z8pGVIz>_0}BzuMb)_>HzT1U+$a9NFA8uL586UN3)QCf+KU^v9H(ne zzz^scG8*1M9MzNwdgiu%K@4C0>oc80cb5(+fx@%Cjw8l`yh2jbvk34?jV_v{$Em^_ zd(7D}M`cIZb)C{CV*#^%qgxPB9^%kI2~#EmN7wD5 z*;abgWo?|Yo|g!9l|%1^4i;{hWE7UC z<5+KJxs4Lb2;JR*^4N8ap?OB66G$^UUQP5>BlefR6Vujhsv&9!hUx1WZfGxzr04c- z&o=kUu-;n0@W`HHA3-2CvT95XBpWs`guJe9)Y~kiT?@{0J>m#cCox&8-pITzUy1K^ zRrclN_i7%UEiI#SN9~yLxqD%5^YBE~ zmzUS=YC1`53H#PI#=rvN7mV0%h0c)5?cikt?BOF&)~osVaKKdckx6Q*zSjSn;WOp3 zA3F^!JG*Y2f;w6T`W*sVfqkG`rwTOGrXXDoVI>HV1;XU?a}RSDB$Pt#q{vSYl8Dl% z*@EF0y}(%w>-3k$rW|0I4V@&vkCWpnykAplS`t!nE0b}6% z^ox^K#4m`ZbsD`2maLEkhxvYyznh%9SM`e3aBx?tCvO}7PhekvB^}YIBbaQ9$KavAv74L_>*z>XQ{Kq50V1Y9QP)M}2!~fBupo0A_`P+;+xBsW) zpZ{+1EA#UGXWSsN0({79*5zFVdzZ1F?nu+SK5R&%kqG94;d_K*2}SF>NsSW!;w@O_ zoyX^Eu;&UcU7hoLuj&Rj3niV!nn%;w$s-?~JArcj-#zUyWtDQ4Vc9B!J8JvaSLy9- zI^Tqg7PBFnBM!5a$}`E|qn{dr2}kPcy>J1BiLMD3?F%peW@c-{MosU7&Fr)RVeiW? zMiwum)^M(|ujku}Iz2Yk07z%lL1fO1iKs7Huag>}s8tzE5`pNzrNJN~poYc=RW)vxkNFd34f5BS! zwwyL%=pTbWd;VQ5Zrq>jY!D6TgFu#JJv@0puR2@j$|K`>JEv5ySqXg=d`%b>1ten- zni6+0xAtC~Uakb5BUEqH&9Fz-v!p^28|Za=C?L#%>?9zsR$9-N!s~slW8c;mPn-7p z#5(Czj@It!8$WFrN|YPQ<6k@D7Jo$)H9p<@E+@`f@uic^zvb07!NuqL0HgVMJ>mA` zEl!mxaPl9e_x{3^&#+LB%<*_z?st9)ooYqW?pxA#+cC2cKB>DGkMy4PrlF!OLbgk~ zq`aW_004-+ip}F?YQ--0Dxc?~pUjk5AaK(p-Ad8cBpm_L?~j7+V^TFn9V81c_+0_VIsIz^+@%h8^H-|gWC+P^*5R=FVawKLv8QvT z@u!m-U7*1cs0hlqC$5XW4mNJ);ne`7vJ&Sa~64S zn!8`x_6^+j%~pYd36(nSJZn{(nzS#ME^SHSUx>j_j!w_my`op0{%W9KHmK|91=IH$ z5>c%F5|S8F?z)0QGK6T6zwve-W5aDjUlo^T&iy`S8g{3$Vk{I0P9GCE!MTCcjy&Oj zAAnzQ%<&jS`Q})th|>GstXk*zY{%d{0at}4ht_Y&Q8(!3Md{H|$s+NgI2QMyDCeQl z}*|zn^VNaTp)jfjYrp zU3&n$y!KAu1?coMzg$o3omqo>BH8o)1=Qag*uVUP)Mu44IiuwF&u2`7jbFaSBjbL{ zuI&*oH}Dg)`74e3S3)4*x57L3F1{*N0}Ib)_u#g4WmJ`Lnh(d*ko^#L8|^WpX)KD; z0dFDUaTzkj(O`8N3}W1EzLem^5u)pDl#FKr=BBdQpl^<5;~UH;nC8G9wAAC#%7oi> zf(X51O?JaNzJUuaCzv6NT81tlq$c|)tf+SDERpz<$9`U?C`ZQXWRP!iz{a|!p3#cq0o@U(8x#9ynyzr*{W%8L z*>n!ULN$5%Ec@($^#Xjy4WaB1?6zU0jU+zq_{#y9ITvat(>WJ@n&o0LSZy<5GROy) zM$Zm6Gn%b^ftAFlI3YhmR6=^qPxd=t#R$yTNv)`tZJ?|{YZQU7Iq zm+Hg_n=iB=L|(_<2i%-`HmSsppqO;Mp3hB%K46aGrMx|z3Lv;n_8I8YgR>#*fJ&b@ z+++BGkpE6Wr0D!#v0Ey52<14g&@xYf?l@mW?d)W@jAtAE1>F{Bu~E~|TYG%&;tS3! zwM)vuoPg&kd7CWm&#bIRw`0+CjtN!*aBKA7vdyUnHIdK7Rjse}tMr(eK&f2v@&dQh z)TJ3JM@fWlXR~hm4zZwL%2ss!@0wj-ELsnuId811h6SQfHV(P*-&TQDSKt^94`cKS zMvzJxHVGW~K^Fqf*O2UE-DH-ioiEf#-)~ zWH4b(w8Dx z5cyX{n7O>(vPhU-XojcpCeAPa;+8H(ehfGUj8JNDdnT>u4FsS{${_hq z=%cJ{EvNSx;C>N`Z=2*P?|V8lSp50~4hK-S z`4R~~MvM;MAd}~RL#L{*TP#yeY%oW}s6vMb7Ska|5GPw{*~SGoSHR&SBgrVY|iAW+>5exSmHrkR$pxY&-AgbtMi zPehx<=}_0d!D$N|C^3`MsY$3h<5+|HX#6FOYM zhloFRY235@yi$0Qt)Buv`Ln?Xu__t+33*e%WbkthAe7oLF=1~5?W&e;2!AAW{TYQ0 zZ&}I$*NZEGYsRV8(b?HputLt}?-Ccm7(Q$A!_P4X;bC7!@ebwLuP1w_nhoZ~tuZ|; zz=g8sc(a=9a~>e>HmdzSXoI$_Lk3><2RKDM{kQw6yhDre+g>pBn7Q!1sd{apBodOb z;E&!f)mriEOnS=0GsoM2k{a=QFJmsLZhR3uH3gWt;>E_cV75VFY)SC;E(L2(s4_!n zdtYL~8)*htu{I2RDJ~C~QT!+GxDWWa$3TZ3h#8HUvN6XeUtAn;y6A9@p{1)L(nS9V z1V;*ze+x!>&pV)Ia$6>%muZYWpalztiqW#`V)jQ$TpS*DShU_QHy4k>A48nOib1?Q zS2`Z5v)yJt#yVGlH$-6@Uk+)8-mF0y!ubh$SWCxUw5;@2u=w%Pe-D$YY|$qGD|^DN z{}kEIn)QXI=v6u_AGTpY)p*xB>%g`XW%kDx94nPfnV^6=i!ZP~Wf>Ip z3x?Bo7Vj52PnB9SKeEMj{4m5QguydDk0050ME%Hqt6-W0VivreA73s{3%?W@BlFYx zWz>HS%H%9#t~OVP+4h_=yP_QLn&u?>IDp0Zhm1OQ9!!jl9^uw{0ebN*4n^D(>+Xo3 zEgl&3z|@%5FjtS$^N81-F@3S}`ZSeP&%H0^f)WmclQ-Ik%?i-8lso&jB!KCaRRM?x zB$7)MApV-JO8O=Xt%`wx&+y%fHAVSz0eDKzNGkY-RUnoLFJ2t_Nws9wXdFF*9vJv? z>-)ri#=F1jde#Z}>)U(t(B(TNWSOubl-+%6;44xqXf%b#e?`sjHDp6(5cuRP=o3RnfMAOesRKt2u7J3vCFiYt6Hn2)wg3Y0l77L&;Pv`HnkIS7aiHXt`etnFe z!FBC&2GX!w?>d_JMT^5NW-K{hlWFoNA&pj}tFPQ6?#QBr6xERAuETL{L=ipHCE$785^oS(RwMGzJd^0j zMuR4ukX4rK${Z*D*8)Z)z#aC`eKa!7ki&na6VrhE;t&37ZQs($k!u9ppWLxkfmuL2U+=V=H&hsbY8iReD zD}$h$55uAh4t771H<5Sznllki4lS@b7tdv(nJ8;!e(`p7+RQr^CXY16Ekqd-Un2p} zNRFJ8Uw~Bj%VHGkGXd5z!%rDfe2><`KlWp&xn@yI3uc6x?YV^4U>Vyv!6zpp_! zrUYMY_xqmZM@A8I+hL(OOQGsc(oz;6tFT1t#DmnKviTeJG*PF20aH++U{QPPn+R(2 zlj|GAEd|MQI&RoEZcT$w@hbJ4+qFLod?ji0YUOVGm0qdEW871KuGKFGQ)2Ti-z(<8 zXpFwo!?eXeoI!(wy0%79jBQYEg5ENP0pr2DI8#oHPp6&1e2D3iY#2G+O;n{10%vsQ zi~3Ka1T@SN)`mY3qzVznYpJtA!~;O4MqTB}KhQ%6!M}fux!vB6P|Hy&syOE3i7BJi z%|<~ID=NFlqOatXHy&vjH*Rq%HQfyanC_tj-x}|_sArc{e2i6>$uf8vKU=Bw*WR`1 z$Q&@H#=MM3Wz=BnEc>m(6r46P`1wpWMBt2rvFXFnB6qeu3G0367F5vZsaF1{gtSz( zft2LA?x)8`8_ltp4ik1w!}RZNEf}(|lpQ>XN$YhOQbU)!P}r1h7q*OIHziUx73UQe ziV@2k8l$W-z=}H_*Zp726}-mi5Qu7bFOP8d35%pa1-y$&O@kYDlt4g_RoWuVazmI2 zOi%`~*?vuq6rZe8&+OPd6%{dQ0F$Y`h`||$e1qt8+Qhv}YKG`?i+_ZU#tsM4urRUJ zp1i)fhb1|aTGf#lTLhE^tbd+DuOX?$nvXwxcjwGhk%QI{!fOGZchvj1H9N*yx7Cm> zU1WS(IbL}La_kGRrQYdGcjBH>mw^YZ{7erjrVia){`0ahsIuC7cWzxy>FsYUW0_&2 zGheS(Q;F)DkU!4n> z@>Opa(mj^(4_zqv8K&QYUSKp>kz+E3yDMP4x_^Q#6Sma@cJgr1U>~Hd zjv%j+{c#XS7C==MypIAp=2v}l-HjHU%-ZL5jnlsNqEdL8U6A@o&3^#!ezZ>flkHCL z!Z8X9cUv2e$3*E?i>UbAUv6OgWYa;~#GIz|xRf>GeuWn!z~>U%HZb@;TmC{I(c~&j_yc46x4Ki>1OM>Pb)`Nx98X8G z75EJDz}Wk)SRngQEdR2O7#FFBT$jIAljj}DX{RDXcEi?D{M?H`B>53FOr_ODbYsrQ zr2(?f9fiJ_d6butO>}o!>u~|%k~DX|Cr@%%{_}9w$fxTwZim`{51508iDaquDW97- z^Ve1sVCv_^dYy_EL!7tFwe79NBm?vsoQYpSSTJanol9=8;O;G@>GLxR*QT zSp14eNH7!bo4@D#Y#0C7HkiyF$y`v$#tQ=))Ln*)!8|tAxo_k|Twjm;tuuKbjtgWm z_HI{D>M=Q0>e`wr^Sx*y?Y+aNDVzpu&Wj=3W*V8#Dh;rQeT1K*mv7i)#V!4G40P&? zN?T$V%R61*DfrMA42eWE4jDofNxn{qe*N1+)kzwm(WloVzHXokRM_on?|Z>0Hy9$J zSx3NawIpW|YtC#Je2E-*Aw%)#IY>BPJCw|FA2s~ZVIT!@b%?_F*oZ_ZgPmod zndE%b2clp^(>{YAx_uLXU%G^HhEt_1R#$)tAE4FPmvy`t%{z1{h>FBUduSyjy2RTD zWEQjQheg6xq#Z0#nCBttz^KiCHQjYCTNKH@hJC#clvg%0HQvP)!D32>*g}W26Ne+L z>W~MC#p~vq{R#br*x?9$_%)|VXcN`p>ELt7ncsO7n`Sd60xPILn(dNRS!osc%QO<& zw_Dl50Z~oSLFDg-$F`6>nqyVsY|kc*&Jt_U^K+$khd6lIMAT;+!&6u6)fviCx%(m- zelHHq{fI_{#lYhAH*<<-rw*RnnrB2#?&}h<22*d*MFKSwp!IM<(iM2>wICyK96i7fEE{tXBg9=$L3;XcXCGVKd=p4*QdyCh2!NO`eWT4WIb! z1P>bPEPrQW!|j4fL>AA$^HL;7nPCiM_s{ zSi3P(WSluNWID4Vw9vSp#A1|AS8xazVV3F_gAHo=P|x_lN`8KrH6dk?p6PzF3?$lu z2s{XNqR2@zo2y$5M)ep=t+v>v=WY>mqGEx0%5v&5>D@PE4Y5PnI?1IK9I}i34p$Ek*Y}!)yhh;sv)gbr zf|qdz4hkE8cHhu>rc;ad6->?LOf)}1GiHZ0Rk2#ZTb|hVkJG>~>hdBHW0E64pQ4WB z777_ra>l!zRy};pM{8?YR^MBGz$nSi{@Yz+9331&>V(zmTFjucq->BsZZaKm4UB~h zp#W-{C8@B&fqQO#RzIEB@B1eJNbzo=gaKvDxt0dwuZy9ZKI_R_` z0e%reY|BqX%hSNSbxIi**g|@;@84kRdkEf1&ImoSBhtro0`B;L3WKKauk}?VqVTNd za$0>H=ZyePiE==FbO^LsRZ_6}j+H;&C91Paz4^!MzrzMt>4`U97Ro;y-D-7;Er3n? zWZ_GO4}))yqm=p`gaBiWmz>kp2gN4maPL~!Zr`Hs#c}W@?yROQzEMshIys0Hj@){F z0&7Dny#hedbg>tL+|Hpz*gOBHr}TMY^2lO$!V>vx|9H){CgV+kGF1MnxW^!%NB-CC zmqEoQ__m14MDqqqPEURTG$7`{#t zRj+JTOBJ-aeO(jXpGih;99DYPPUQNLU{KAxyzdL}->yrDttD6a8unRz%oarj-B3AIklTA?H+MTTQPBpF#sot$^ z(iAGWM&R#x`iybw5P@g0E_s#p`o#<@Z)Xv-m4NIsj#>|#-|$$MIhe?5Z9}~sKl1=@ z#x%b!ZjHsx99YGD2i{y8EY2MqCO5=TPCq4Z)q6f(b)fpt4*zo%CB-OL>&ABmy9+vG z8Fw!PpA50*Yr{UrOuMaa0I8l$hWyDduO%-Jv8dnZ zHRA1zjaUh4O+r}x8)g=1;;(z_u^e3rI`1+xJN_<|?)t*MuBd z;=RqRijpH#dOb9oo_~-n%@grl27f48NqFhrYTbzYtvup!x1wv^Qxq1b4FY#xwS4^I zxawTmCDTo-Xj1<4*9mi`v>k|YB|z}TY7Eo<*|BHKJaZ-*=FJ- ziZJ`UFUw^LWXmw1(U6uSq3L~#dMHigRY`EU#_S7#PtVLQdPBMBO%aDp;CX6wIlLIc z`bZX3B}az8ZmV7GrbUxcImq!{TYry%U@sg-=Ig)R{`8U=O&oQ3=Dp(58xc{7Lj8}M z6Bb6kiyIa;T(bE8I6j?Soj!IMY&x9@^~qA!tBbobzA zz2*4-cmh!R{H;aARgj-5#>lOTk^j@0R$qEBup6*v1C~PkN@$^-~)V6 zU*hA5e23-#*YEoO`65(r8+&sArUt&ilmYOz-{vpjafpCKtIeAGQ%`(OyOfjv!`@p3 z#q}-P-U%8!SO`u85AF_u;F@5;-QBfuNN|ER4#C}B8kfcr+}*9wMjO87fA&82oc-{< z->O^11Jzx%TGm{><{ZB<#$3Kv>fV&}Uu>{VUiOAcbg#5++k3ipr%hB_E5)yZxb5To zo|cI`GmnVI*kP6DPHUZA(yDa!4-I}d8Pc~KrS>Kdr18E%kHfz=lMA3NjB)4vjK>Sk!ud-zSYi zMs(69aac&M7ZVN-L_Zcuzr`1OK7%sy{@}RMQwBYsIKf8;QhJp(;GxVNz)ZDCuUM<) zfv9+Oi$WK7fxz>@VsU07&(Zh7dpxrpPOCY#<50E1J+@l1XGUMP{ghl1_<3}Q9LFQ( zUXjV`a@DNPTvxqp-|E}Flbs!)_?13D!FWvRCtKkAnCLqKLBg|e_-5<5@fZ4wNxMNg z#|Eua=@Z^$+}0Zzi9qHh z&s&!JeF>AU(n=+XCd{Dc%dLhzfGmajf|Zn%)g^c&l|u=gh=K0PzKlNVV3rLrzfUeX z?fZR8eMK^kNCpk-yHZbEHOtvMy`fAYZxSTySs1;28!S`D`HJu>ow=fC%lM0-c-j~n z73T~{$CDVm>&3*gPS?7*YNWY;H3`%x`?RZ{(7ehN8?VH#lDfR2SyAq#mdbk8L(V+n zm+5Qg@o|KGH$AamNx4bP(o3@E!Dr_a{*13JWuhFT-X|rY$0`HKI?cFm)c0%ygZM(3 zY~yiur*9udp0?U!gt-_M@?{Bl;6Vp|aXs%%+xaTTc-0bzYL_WNBDz@pPs3K=U06(^ zY>!v{cCLC|x9TMt?d(y`4ky!3o;^f3%1;z_lb@Ze$bWT)+K|D7erutzp@03F@XWhex*-3gTeR-m7KWyF(Fz!W(VM< zt63fL;zB*!$Lqw>yl;&w=Z6qRh1c*U3&S$M(0kJ{A#J1L>mntwE0oek)MXzh| zTb)q`6`Vh2ORfU)#8(uo5^$B89!KO)ckoY_-%1B7`9Zg^^QPHt_{w&BDEioRLmOfN za{pzsTHO;*Di~~Q?Qp*%Ksktn-Et)+3_)=4JB34!`0%ZEXV<$<7p*C;QNHA*Z{ z2sc-Kq*#AAM7TyIQ<{{gwyvB03_wKDdaG$DiYC@}OmS#MkV_MFfPvx?BQh;#5Wexo zylF1%yz@iGDAnyuD38qi#dv(<<+d%g7W`@|(J2mOFsDbfFGJD=$ROi?dazOdf(`f3 zrqVM?C_9vB$T;2)lyFH_2N(kZLdEC=D9ll6T@Ow(M?Z!#Mo|-TQfJlUC#YrL3g77J z69T;xp5PrUCc`u3+yW{f!=%Y}h$!3q2o-GtL{jx6iOT)nmXR&)Ct=*fRXyGohl0F3w; z3jmUR4xBK9g~1-TXID-0o%*w9m`Z2MIKaA-izFnbh*yR?Wd!(wQ-uXgi$h*&*(dP* zT(^+D?aL&*Pt?o|yzbj=kxGC3a2{UulMAD5Nuz?Dw^?7?sj0Vu;dQE!=iZq+Jq#P% zOeYYk28Rf@+wQ&Uud1l!MKStgsUPeQ4T$iV^!wvB5ySZoFnV|9OGmRD9?rf(`boE3 zbjd?M2Or`q@(S;GQ6@B&FE$W&(9zb3Pj!8--Idw@RhV`Y+h5A4Z&tx(xGpaMMv>iA z&p5SoRr)rR#gbJA*mKOK>!Mux9Cux^XgIuA_xsBhx!R{c(Nu6{((KW|2;){$fm@01 z_M>wY`{&lPO%y4^MUo{zF21H(W!qAnhC^T=+Rqu5OurL8vp(o;q1%oEPhF1 z>`58COY!MaoqT-GM3XJXO;gDO30&Z43eT#%;?mz6Gc~B|G@oB)+V1+|;*Xzo_9XaW_Wl=2&hWvGf=5YOEZ*Ke{T9BDFO}NfaiQ``T z_l>0)7h1ys2v!a5jYuRK$^4*mq>&28X3gs@Ruu2@m$XiM-g9s)l^$+7`Cc*XnB{z? z9juD01X^Q`#bsqXz$RR_w-fWSu`jb9rrX!O=+!T$IQKjK3v<}J!ey@*G=OS3oYRLH!g;Zk#dUd+6v(BG5mGxP`u1CwSjsdptMD9Dk6k%YKRe3`YB|*_BN>aXEux zW_lv2wgTnFjyc6J4`67dyI16A#HjuFO1&oiygR$&tAxhA)pU`aJl5_Wx_*5rgFwa} zgFJT1!-565K|au!a4<_>63)lMwd8VRI~~g`YjOP*pk(u=84QJQZz$b$D4MJL>!FTY ztvd4xDbe)0gd|{#Om5SKDG4n4O(?c*o6-d6Z-9J)OAh zRpXQVdrVYni8AerCvPwXVPHf?=eHkcTRK{bSL+k$)fWOk{wTk4mkVm)k6&GQSZdW! z5H|OrXG&eB;MeCStF#AkujZ);aTKc*t)T^44}KH6?^A`W4^*zS@!cZ!99*&WY)5{j zUY|NT7H{QM()fvr>8Wxbi5{Boxpz|qezzb3MhTzBYJs3Kk~b)HH)|~SOUMD)OY-og z$FV5$OG80viL~CaeG{f4)>$gObme2v=o{ARjXtb#D!qNjMy{sA*^6Rn?NSfN@6GoC zQu2OHw)fCZz~5Bp=*@<;@%zX`K44#*4p3VD!O z(Gb>%V}tGLJLL0S4#W{(es41S1^K+Yv)qbsvGc7lTY1E42*$p{aK&b`KaEhF0q^{QVNU9?##S%1%MVMx9M6FbJ`*J7SM zB`*)M)FNa1tME|zv`i!OxgFNY%F7ep%iDQ5zI1b8xx1hW+*)T*T` zqE;95aF1o%gDyR8Z~uX>!yeTPM#=>5lrL>TyR$4iqu%KZ@w31n=|9m1{nm$hR~Xwb zN@-u0g>EXRGRHJz#D zU&2czB+20G(C4w>fW#i+gfvMdeWDWvZQ2@jPH*0I7vw@QqYriN|GtOQ+VTP!gCjnc zK|}A23QCoenuG#(;KyGpg3Ur1eIyke3Yjna9qkZ}0Ks<5{$wL%R4a9AEmb8)3)4@7`W!XU)Eam5aN6d3I=s;gxMzrctO`K?L(B z4EL=O@uf)?* z+0J@h?Xm#4<5maZK8sRWoP~~oPou6@DIG*@?~)T(3eDUKQ>=Y>55H}h4Pt=YvQgS; zf;~2CU|a|?|IC|OLG&gw`;q@FRX79WbI3B(%YM-J!uikPkq6j+f}%LmSCki<`$L{) zT!P2A@C7E95+e~mdfEylK<5)LIa**lr}_rg?48m$4~hcNux}-3k)!}f%Gaaa>{WhI z*xdgD9aR?dD}FgOKdFy-L<76_wS;DTSC3$mUFVYw+Lk=ieyqzF{N9MSnh#L3S6fwbP|(I{$!{wc23ddrHyos|W{G znMBGPN6LHA$hKfbS!xQ1Uj1x^IU>yvzFv7$*d zu|~7Mwq7wDzQgdy!I>5kVD$tR*Ph&fzPz<)QOcDwQD?jV>4ADc@5!6-DvZlO{{{3t zGTKB0ksx=3xL7xXSXO}ed%X=T9z0QgmMX7mhrdS7{3ME;X-}~C7dXbSz~fw6eM4uS zkrM*|YJg0tb``@^vp40nEmLVRO@;pS((AOu>c+mu7#f?UM>tn8tL2TRM73s2R{vx_ z0&x&U!JZY>a@_k|7TAu-`vu!mlB9}w`9}pdN zq0yGT=q({fdP+m55x18I=&vX&hXvpN&FqQNW~B=eoGt~ijcY#+SDH~`p4@H|o5}t+ zvvFi4@G2WI`XGe;z6;I74$bJ$?eLLYsbk5o_grA+d2;AgZv`xJWFWiycofysg>TrK zS@c6Fe(zhh$vRccGp#yUu8eFGRG*qQd<7*ntei;kqC&r;erSm2)_a_Ah9E?tGEP2| zI(Sedeh89J))6o{n|l@UgUB{NGgBfQ@ng2X7KgcfU{{-4dS=y6uUB?^ECq*FHf|VJ#!DlY0-xKa|fiITPZ4CUR2q!cR6h`dtg8x%tE$OgMT|aGb?Os@;! z$3cS$mimK}8T`QS#`h2D9CC+o{gr80(@m)HnKQ+vVPb*_nH4SHDGYlOkY$lZNV*)Q zL7C?wYCp90s<&TThwps)c0siUhcq5xpPiJ_q#_x~hU_G4eFG=nyfzt$(R$`QwEu9P zD1EwRRBQ^?*}3F^4~h4OWy0#fQ%vu#2Iwe1p-ZJBRTE)|VqTZtW2X|lZ;Qkp&Lh<@ z=_*FG8hK7)Lo;kOyL^rjt(3u(Lf@*^r9pfj7NhQHCp{0w7Y*lCjH?Pu1guLP2sYRs=aA$wa=|BQl7 zeciR#ED;_X(er<4n=g@S{u^WLAvGz4OYzna7Dxf^joBIS=LTcC7@+9EFxmuZj1jx@ zLjVHEm;Nj??T1gd1R`m^q1(3LUMHhX`cznMSR4^(x<$q7^8z;)YVDJDA2~L5?O>dc zlu7oE4|X4h=>ZWp%Ff$7N_{F*{MJePxx+y{R<)KTyaevX#mfgt;pj;rBE8#QbS%Zo z^HSr#nfLh^rkE#lm1_hb!Jx?w*!}w*54wBxV@e*z`mr9ibVKB7*OrTxXdZn%!;oeo zxppa!zvn0&Hy~A3+^8>|gp9??p)eY^hi>QOxUAL>u9Uvi(3$m9d=~h+34R~Ec0ebC zPUU2H)PFc+K5En#@PCr%PaN!6VH_=*{kevlM7kUYS9wKiaOdhovWR2M`M2rj(chQm z5#7^z30|b%?p7#g)1`#H7?Gy+JFTvqNb__8r~kM9YoZQUe%Y;u*!BNILuSLR0>?G7 zxyXzCW&7WH^b53)B5>h(NI!Js<^O(06ag-6%f666`A3BOKL?C`fa}iGe#Gpk|L3uP z(Kqh z?CN7|fVdQR(ibHd+FMVLMa6yu$teG>jtoSz|{h6zt-dx04iM`c6}n1=uh< z+GSpVJ*)K?L@)`F6hF@~Cvlo4Nrvrt;{OM$c_YUv6{77< z`P%yTk}YrhV{Uyw_9^_!Lk>wBG+$-WBAe^qjAA4{r?6kyW3k2`jQd^_Lj?KOemA76 zKr*zQ$gn4>WYU5Cnqq%Km%?^d%dWZE&TPmk)n^KhCR`vBVph8P%S(QBw$^-)HwczX zO@9AI6dHPNKkrpq09n~**rb0L@%jhZHcby3Y$tMTO9$I0!!t>m#~6rwQLz(o3gzRy zPudiipZ8{a@rGqHf7masw6f`wIGaaeed`!!Q*nci&)<+|K7y$bp})5Ob5(b>^_tuy zA+RDwXp1PL=>l@dibm;E2}V_asqcHn$9I(wFXa3>G;T6Ct@`4-q%H{dD0O}i+-L$8 zz-MO&&%>A<&D-cB8rsBZjKk}Tpw`u!NL#yXSiih;FAx!eC3B>kw2gs1mHN+(QhV?R z3Bb7W7};;@ZCztH+sh3=&a3^L`Qf z(!U@KL79Js&&1D(BWS;q?Q|&;z`pe!Q0%i$&2GNp!&mL_uGrI-%hS{+oL-)i+^6T0 z*;3KF!1-RIFN%p6!|9Aq5BHUAmj_5GXWO&q*E6U@df;C@I)qsxu+34uh-Tnq&idNT%OghFYHorGx4FAI%tVvec2FSF0M&dq%J=2=A zpqL?upHUMpWc_jAoB5!%eVeW32JyLGwkSa9(S3Pla0>?4U92BOQUmli?_BfB7>LEC zqT1zBbNW?eKw(=ZZA8YRAoGp-1!EEmj(7_j;6?&B60CMekq< zi?_J6i3|x5I-3ZFhO9rPY3Y<>1n0hnXL`xz9aVw~!VMo6DH*o|FUHuwOb0KKg4WHm zNGa%B*^6CC$FV5{U(;JBq-bPx**uQe@1(Y%i2#36Jao(TAC>=$gdp3Z;YDGA_o5nC zXRW|=>{EYy3( zet)0?Q9dhE7)z3=e`d4w(M!?{H${PPbHC}u#_;Bv#ED;C?UKVXm(!}Q!ic?3=h8$3 zZf1@m4mn?cZHIQhm@oRm5A9CxKq$u`&)t-6tGiS>(pB!>;mlqMuDUGA>hxEV!(R9d zmoo5fS-P{BNYmA;=^M|j3hg`DL|)y)Ke}I`f$Zmh6ZRK0I+%0Auws2YFGyyKAi6Er zal*^|a{FJYCy+uKA_TK>K6!dM!Wkq*v2sP3BmNYQM_U)le7@y*kQxbHuaFBLVI60ubHRW-36bT+Pz=C9dzI#No@&@=;Xz1+u zwXKlqnI35n#T8pX2;&`Lr$*ZvGyq%PRwyiYpuGZ;w2yGn4-cs%p=vML-r14jbif`q zzblh=a)8dy4?uzauYU~3XhU9#y9ma9qX1+hrxRZoUqXN{LfXnaAB?B1CL2SxtpyKI zn*;?sn&dMc^F1LvQ!;FeG0|S8Z3bLpiVR(xPZdeWTh(C{tXIpGc6=Auk`}FE5C-5^ z91`w{$e2IOY>r+t#QE}gtM?Y+^jXxyen}B^l=f~+W=y#323>jm&# zK2wGU(+-ym+6>+oYk;~6SO((-t__XLcC(HR;jstcV6A@p1*u;SIr4O2_I2G*) zLd*p9L`&DzYr0>nBQV8(=+;*Xx`7j4R7npkfMFeW&8n(t?Jr_~qth)~47D@n zf>Kk(zQN-+K~@P|dPvVold^wmy_LP5nM1?_h}El%V7dzr{NkQ_1@L5yj|tv=N&d=D zXji+req8}B4raK7Gg*W9gB~j6Z4v#7fB6$Fsh6jfpI9>7UTnwDSQVHqYQ84L1T2cI zOxLl{t?ydu!r)BZ{HtJP#@Qmj{}5V&zAR9QI3H2=^sGd}xMp+J?>eQ*##dzhW>bL_ zer2K(yswLTW?TwBfZMPSnCfi@A_pGItuB)ObwVbDEmmxuoA%7Yf`imfGx>ovcvCoY9vzvma#v=!R$@t0{2GTw;}4HUrBmX*l;pvhi=>sd zn0M#LIl+vgmfW*Zv#;Y3KW!O3Dv|5kvxR3FE2>P%REgsDv#({5!u2%O4Z*#YSV6lH z3sN(GWb)v!b?7jDg#$utF4zRUh%pzlrPA`8f1FH``SbSuk44Kb5rYDhK1M68(vwnF zj{YK;b?=lPS=TYphG%(aa4=gyuoLg*i_v63rr+JafAd|XzUek=rVkxWJU_B}p;aQ$@o6UmJ^J47Z>yPBNz}R&}ue7n73KbOjLK*R5-}x2t5#WJLqJ655R`SBm0~&Ij|Sbkc=PZ&dvQtMz(MZ};yDEb|a}79%C_kCA74{~>RF>^SkfkWxVKlPV zTS3t^juCVxi>pgts+QX_W$zR-@|Un(&NUySRPWSy7@?Hmx_Dz zMw^Dnn%sJ+EHOo`!nwz_|Bwmm0DUBl%KQ2xaoi;vnFa%(X@auV-XhFAt5Io`NyOk1 zh)H@I-s}d*?vNRGGDAc$5gA=U%1+~O|={kE1lbO(==4+F(7zbT<2_tmDBhg#=5q9tdH*c^r zrs^F}ShM#R3>+?2+8KSBsXT7od{x2-L|!RDw7z7EI-C`;Cy`Asp~od5k}Q8s|U7K39J2&>=z@iI~$M2a@$=onXFhjVaI_p^1!Rc-su zOJVEyTKE??Q)-$QhuXbO)fW+rFJ$06XTl|v=}Ov(tO+I6wx%+bn(Poh2*> zyMS(azbwL`{t z1K6_2XB|djT+xnrf6&sc+lP8plIgF-?l+ne7Ra|Pu%1T=u2h7Z$Yl@&8T1h|MgjMF zJ^~3@o}k1X?F%nxw!0IOImmVeMm#BJ#g_i;3KhP=(%kMtN2IZ#l5;(;1TrYT?G#u| z1i;jD0+FLct_R@5!Qx5tPgTovp0qx&cC2}M(_MM_+zE1BQ3FW|SYST0>uM@ov2`ac zeIZXLp1QKosad$BdBLyt8`?|OsL`5L2Ht^B%sAGi?j2&P*H&0YcKAbImAC_ZsS`%C z@3yWpen?gDoi*vbyF5D--H%sHN4l$i3RE)a zwsI=k^oD$zr8ag^cQZp}QG3X2G|2JP)4O&xSmRbOx6URrG({l)7VvN@Ux+HT$2*|t zGu$-Ld9F=<)I)&t_3^iOkeKraFls`|&-j%@6!#T#`F;bVP{L284!rHg<7fo%%HBe% zw8sS_eoGyV38LUciy+^_u|w~97RC|VGD*>f!y_!Sy7XGG-a~mahF*z*hgtEjQ@e9# z$sxSBWN8KRQ&F}X-)$=~+Nfz1mhXi$NOP#c-_=HFcE%Ei%aSx3-;%*e;6GW{IZa}C zV^2o&?oy?i)+piw(oWzZ04h@S^6|~XX)ZC{JgcmwSL}d30Jgj$oBK+%`C%=7e1rpN z|JlIJVp~4LGDbr=47bDfTM(zoApy#mhZ0U?cAkl&BDsN z1Kcn|x@DC`td@B}#;$&2-_0dA`VC$qrf-8DtODDrck27oiSe0$oZ?T2$o+BvtrmIV zsfGqk)WWx#;%hjvgddA2tIj$vV}cVTScWXLC!4%E*8I2 z>_{fWWH%6%kGl>K$`Olq)o#q&-?nL_gK;0UpYJ^V<{;ZFOzM8wK2;8bI;KJ=lPqsv z0Y2XQ2iEOv?D0c#vt^`uLwN%ub|QQl*85<3YcbuIVfl!PqJIop4sdo#=981KeSO zzS3pw(lE}ryN6Q}nK7{K|^fb9c` zSMUm86v|RI);|BA_X!E>@p^hu;R<^Dfd4%Pbog}bM^QS;h-?Pb;kEs zAEJWa2EwcjkxCtaKl@&wh5yn%iaebKg(?-Z7cCI_-|ugsaoo`Q6wKzX#>w)ZY(5^HW#7ZZW)Z7=2?U5|V960ZXis zMP8<+wc(n|w1dpkGAwP@1`L3b)XSc{l@oinLXjVpi%fuBCEu{)Rlm+d$5d}US4Mfz zN!cW@tv@g8VmZcI-e((5;pIsLzDn+UlX}}5k_eai**9m8@U z{~l!M3xum@J*?pGOS8B4i&4Qotnj{9s3RItG|)#?l{fmD z2YiQ>c4RtbpU#dl`6FzxPSE5e$4nP=K!N)TWm~g2;1+S@mTJ-ch;Sk0hnx{1a}xOfNhrLHqF`hpfRg&m&5@# zmW~oH z@XG>2J%9G6twchJrkIxjwjF?H23;wQFe7$4e4EfFg+K4BwQoJs*Q7_&N*?WZY^k?}8t+LpQD076o-;5BM^ z8g`_%fnMYg z18{t^QC}A;o~w;WxuFWh-@ui!l!Ld4E++L9R&7I$%saH=A zIH$EeEi6vvLF6_u#CgFTsFrhOZzHI5 z_e864iTAs^ULkTuouR8Qy*YT&=`7aMobJImo<_6pCtWgpthz?s#zDS;s7^9okGw^3 ze|zckFpqfway!Kl?Rv!$Vu65&3ca&AW4{=uKVxq|rPbsaf|AR%_Vzoy8sS$Ndroz-UHAI_v(DQu zW|S89qPEGJI4vMm@Qb_)y8EMXFJY~SS5E0#$fgdja>oDjn&<9qdL3GTlQD8(kD-4! z;7p5hauH)pX(+x#rZSP%2*Gvihjnwsya87_naaDwO1o;?~ zbhEJZ%jY({DX7u0b5D}v??WRX``HGfH5Of~M^>&#zr>n!20>pd)F*>iq4Hx(+i%BO znt&pXxdG3D3hoWxkX1K?Pnm*UA}gD$xP5a-5VF<*D7p|D(Rj5}(6zbyfJiKMiFS2f zPXh>^=H}7HGT1@S$2rgJxM!v56shXpqJr`ne*HMp6to~T@=D~%Dv8)Tr`@oOQ1YQW zQXTTJpi=idqw5UUG8a;dKVN@=CX!M|lzK5!- z!=LtXcJ={>sVS(2;A0U-Vev0cqY>V3ENgFEacWIsQA#}4c)b#YkoJiuDhnNh=4pTE zKgJOODP7>PPn$Pki*k%fJc#T%+GiIryJHWTzGjyD^H*#acztVl6BYH3#-_g2qUm;{ zSOWt;OL#Z(XUbf?orzp24R3!c#B<2IlUL#6oUk6jvRe+Jo7Vz#k*voPp%E!EQ#_cg zr{^KJbl{1G`0N(q^5aF`z=LpC{KLk0P_jNPB}lmM80VOb`_}C}GRK{PC3O*Hy-cE%>m<&OgkKx8kM5)Vx*r{VlyaG!9jR+41PBydt za+kkP9JAUunEuUFNOpIE%kw%?j5wRpBI)qXfn(&WWUShQHw)bN~f7j9^Ghh z;%W}BUn>gNd$Z!|z3mwDP#LIlnownurqYzDN6qX+M+n0;n84XYmLD-ZC-T*RFIzU? z<_Pl#3ngCOA}P@B`@);wzM^A)ud$=&2E>qGN6u>ZJ0bdK7p&^x`X&i3A| zmh8mFLy<>pR+^N6;Zxzj<|;8a$^&l_F{+wJ{`CF~grwnmjv%}$mV)dGGlkU)n3Y^~ z&9*nNqy4G!eqLD$t9RgKx8&`dII>TA%A$0Tx3fd6_bx}Nsy4ptE(gdufvMNh*>2gW z0P~8*Vilf5x{7l8%qO0sP?=rE78KUio?JshAPf~dUIal{Cx23j44r1Ed?qSSo! z+lMlaRAA*_w!Ts%i3m)?uuaNLR41)0s0B&rR{Ruer)=r+HRu26=b&ZNN+$EV5G zf`;S@fI#qAiXvS+z%F-MLBwzD)G=eFWdoZfX%+sJ&W-zvh@c%vwLkP8Q0&<28C~PA zf9$uwQ_VajRf121d#RXI!v0c?z!yn!S)_8rv?a{_UulxE?iWw)omZ8nAQDL$znl6} z8?)UrRe;e~*lb01p7*Dq!(^0ey^o}y%N+0Py@QE3E$uch?yxaATtUT)`gLww8d*ya zG`jM+_$a?fxm){JH=z2*v>ReMyw{x~sxZ}L=QEtGs-@*>7%a}%HlFHM{{6FqBJZ6) z)~@Y|^_!xm{IgCyvg_O+6YnT0FY##u!N0s4YjE$!;LD)o|FCj=Plj7O%Cw;)a0lQ2JidR>i^LD8n0mkd z=Yw#2m@eGmQDZmHvHKs#!G=Fh#_A#SzhCtK=gpgtY+LjcfVMgV5Tncb0L@mTTK|L? z=bAa(#t$|lDm&uN?B%%u@G4}wLmrz*#`F){^zFA#yIFoF7fGNP)pNwnkO80y?2o=QcJZ~Sh@DK5t}Nj@}S+qp+O zXyXOG-Y#AuYN+_X-m}z)XZ^)iwXaA$NWf&mn=(J*tY0DRIjU%KeUi{+Qx?Efpq44$k&T+MGjy8OQZZ=xxueu z^|kW^6tc>qOm=;|NOX4S%5wh4(b0UkVWNFp)Oq**OT;$Z7r>j@4`}urexoMZC?QBO zI%502r^?Ih4=Ynv&aiH`xVYo;+ZKZ{~Ag+m9I?Dw~HKd zK2J1@YZp89nLZIV7M-cK-MsgFSm*xw+oQV3E`C~USvz%zNZ$`%1`w2HHH;tAkZdzw z^X0@eG~yYa-zl8@C5%#tJMvhL5OmQITxh80wA5L3r1edPxB2~g_n}_`K?Y^#uco0I z1P(;OA8;BH;A^vlZ}66#Um|*Bifa}}5NsuC$iGIDs1x2`T>6k=+5vn|>A~K{mA_)s zGEV0*w9kGH=}bXSlZeyx`3OigVNeA}Tf*-W>(L=Z^rHEoXf_>AuL< zE|i);Lg&bWJH6ry1>9pV#`uN>gNBx_MVOP!Tin?|@C{O#$3eVW>JQMcbZ=;i(^2(+ zWVdGNddC^m+i7%KLpmp&FSo$**_-jqoFBWt(6nzefBLXGrcYNHMe=@tEM7fq86FY5 zJa}rO0=(MrXi~r!Dn47&Yg~cl+m#<~_b3$(>8CYHk6M??w$5BX(_rdB`;TW{XCux% zrnPs+7cRSla2Yi9y?y(A4rhg5ZmMXz>B%)Q?-cMja?*ua=Q4ie!K~t$#P6hVO=`m< zaYJy*UDvX!SnS<%MT5l>l|jd0KB{8ImnXKoFmc%s8s~A;_W&Up%3jaig6HhEdb>lm zW>9!6mt03SWm2QZrz3Hf;>MSnon>liq7*vTUz8H|NVXfHEDv70PJf*+>2U+}kky)l zy3R+JAdxbrw-L+<(Z`VF--Ve*ygtItNsa*hMXBwHaH6!9?T+i-+~ci=R!A~9S?|5` z&M42m@8dj0AmKCTkD3yqLx2No=5K|5mIqeiYIKLonv=mI2Z?8a(@k(Yg$ewXApe4u zvG~y}RB+t<#`?|8=0<+O81r|iGs%u-F&P|f*BtEJ#lbS;3RV!A@A`QNGwG` zY4{-4ESAv;UJBXGGAAGx0l@Q}4j^QEmt)bXLu>>o`FfL<^hK9bwDkF=4(Fy&NEA%S}1*6 zkKT|8ZZ+JP-k4>Pr$I&pvi#yjIXWz_2*r*-!fFBDnJ^hrSNEH{+HUt z?Y!;~8+>t`M)NXO9aG`eI_T20K9cB<*R%s^Tml;HYuc zm&JW$LHXZao=}d=E^?G9b?C8NaZu4LEty~2x=%}mL=xY%jk9kz+CZTp_LsLoSLG#| zbuER(TjR+}S|XQCDhaR6IL)~AuH8BPlnp5SN|K338f*Yz56}Tw%bAjPQQXUoT};Yw zrHs_s6z!sWWg6w(MZvpZmB0H{Opvw}^okUuYU?<{6 z{Xawgy^~M24%^G1c83OXU{l(^y8witnBOUJF%O`N3$$ds*|J7GUXqSca2lyQU+>*m z=hs%c@0_!gUU)pH_!+5MTtB8o88n9F6qCfY07)tjTS)&f-1hOm=1KuYNbG&!I1T1; z{@esiI3Fz2OrQg#rfm1>VU^Lf^Lmwim)Tn`k3U-|T8T0fZ8#mE))Vtw@!suG8ihSm zOR*DMti$0T{_LQyYqS8%yX{$#5d&1lud{n=ufK|-Hn3Rmx97oAc+Y`*NeaWtKZPY9 zRh7tlHv_OR5JyR_(u&SG7t5%E&oG{5BpmyR^hs-3%Pc-kOoz|#$43OvJZ2lHchm(= zwRG^e<;C&P`Wc|uSH=+ACD04@VzMA1j>>Kie^K{LF};krDAKFDLsn?41Qzz;x+QWmI&p z8kHY_Sjg_Ti!uib9Urn9TGxxWrFwngnO1GBknhw@XQQqk99uT4LC>wI=!Bo>8hJXt z6Aq$|gb&xNRo27^fpR4L@lk3*&@&Axma*BCLkzZ&85NXC?EkcT7}F~Lg45MV-fJWE z;}US(E`SK|s0VKt&9+((ggQ)YB&xxPwU6P(V$}>50T8dv>O!02}!P$i7nOY6rBOEw0cOpSYd>*$yaVVE6pL`aKW&nB)_#@K@s2};~7hwB@M)`cvxKtm8L+8|K zgv0*Zc16M(xG|E0ND*5Q*1a0uXSck>=viU$`-=>nxFEF*H9`{0DE8Xkwz}gIUZ!Pw zFE}NG*^%>B%s|m2rNRZ)WKw(^g(G5yiz-Yc_7Akyo7;&}U>7z#4v4U>D0Fn_niI0}wMXL!EE7 zE=--vcVNyqKmo-;IOGz8@q3;ke^I}uD%4QY;<7950<3yjKcq}KbTuHHE846)#- z<2Gh?H&`C7+-9uYX{yljDUYY)5x{?O0zBzy*s5Bfqwpro2t`iO;$Woeo?yI@Jkll4 ztQ6jWlnMcbvCFg6f3?m8zA_=HZRwOhpdvu{vc5VsjRB<3mx3OAsw3a($B)*<6-zxb zpp}A(BQ|HVvN=b!E6#~H{yM4d4j+5D?7mIQ>N%XmKN!=;BA-;1Lg5`IoC0lT(?%26 zo2zxNWDB{F{pPHT`~R``R#9~>&DvjtA;Xh^&bIZUpHpkpNq`{&2`nL{~!yZ|0^txgKSUm-Z~!Nlv+x?SO!GAJ_U!XClMOu)I< zU0k!&oN#DXSeZrNuu0q zs(~+|4R~}+0)_fZ7f(E!rDa+5S@0t9Dab$tjtf*3TMwL zD6S?WJlN0S?Vt2kI20g`>B*V&yJ_O6I#*q+0g-T<7CT5guvo@`M4;D67Cg5h-m|&F zMgayW94+g7s&-eWrR4i+af{klc+dKe8skoHy1tE>@$-S1$bnJ_oS?B>BNq*Srz~tO z*ckHbRY9=Uz~+Udj323^*b-JF1R=2=aUpMaw5ASa3&=~zELn1SuXDk?V^JHwEBkHx z8^8AMHz#6hlO_gJy6e5~sqpdjdBCk=Pp8YlH$RnN6fTn?yHFTauFHcQRG%_Ue8qP! zN4~nb3Y67Iqp6HN__xkb<)0pdFm44@wH4t%Ugws=U7@^q6G zzyprpmc;%O5*k8U^Hiy*)De^Yv>rZ!2plO&VSEg&E+NY~9iMX+BL4fBbcHeE-i4u% zP)BildWkj~sbd_|A^ZSbeT{~xP2-9+_djLtF8;RA3tmf0n|u?r5%cbuF_x2}lkw~a zh``Od2lVD(8jx9NAk3VL8vaSkRw*f|>kBf38y@goEjbm6?s4$FVT~B$i26$pzl?VF z_ii>6h>0qtN>zmIx;tOuV0v`&)(|)kpDJeD@cs4T5rD@oWB@*u2_daR*;m=8XWBeE z{h!#fRf6xoGgX08_8}T5@jFP#!H#^0XAB6ZHPNG#VSI(1Ukj+dlB#rqcA@|HheqE!Upq?PT62f^@^i+K1F#ocC-ydcn ziMI48@hvxnK>T8-|M0zsfx=+bQN#>n_s7@!Ir-=Zd|gpN9;G# z@*r}2B$ClG937FA;sg1Ce6)fCs`@z3YwRuk>+wCFD~R@(?sk9kC!R-Wgj*NV-%fkz zC!%zGwRoYZ#KFOQL=*i-RJMwK=5gDZW{+@gENFv;_lEL6YCU3XFEOLy2H^*Te?S!?zj0cc{MHfbu@J%xVw^p^7$b7< z|9HAuN-Km+rD?@0U|wO@B@7~1qkznjRzf?yPyo9MXahhlvgP51V;@5l% zd%a1JXmR9}&!&5j!dIcmHd>pZ*yVDGuaD&#JF>*Dvuhs3ha;5Bw^+(8x3dS4=a7WZ ze$5|j+!lII<-}C%Z|~>m_6rQR1JJu@nwSVGuwxy;0v%pawu2oMp)=*poA(PXPHDw9 z@He83mla;d!a!tE2$haGn*}pO{%)Ii430wyt}T-f{xyTl2N`wr8`u0W9EaPG0+Mw+ zh@1-kRo}uOkjj*z5`xrmj3-a|4`areaW)wo`p)7Fm3q70`G8m^_o|TanBH|O{z9(g zd)V!E_05`gbKp>qGrFpWG%O838F}Zg?phsVkHaJ z*5JoL9^B3wC$a`n*D>C2%uJ4j-#N2a^B(r~P~dt0gN(3&FLGF~xu5s_zDNlWgMT*j ziJB>OK7z-k0Tr~LUgcSJoTgIEQUb?|@kODdplq0AB|Hwa5rkGR#@ z#$<}iYK=6-P7apOcJ;wq5%YUotQ_`D{iNcp^y0Q|)ahi_xM(CeP8ZY#vW2kr^$yWK zyPoEp2DrR2BE3>XU>Fae$J8iOZ@)8PRJYF64(Bt~*X*}&g85k` z8LVSD>xmOc`Lo{Lp8%LO>9|S@=8%gLkmbZHeT56*=Sp6-4stq4xb|?uwc54{7WBGM z6VsAjtIT2Z&=;$=RK&|()jI+%jnIFHyvlPW>P2c%&Feg=`-7))zP_&wpVSC;PX~STk!u7@$rGc=*zyI zv_JaMhITtYlg9Q{xNPQ=ri36Hf8^Xri1td#X}h~M5%v) zp!AV%^verBcdvEAAvVm*mhEpB_4A|^50q$v-!3+ zxeUbrEWZ8lR`n}F%^dm1CIQ8zMihfdO(6eh9~J_&v&5tRe=q=%@-y1P8As)qt-|+z zOvPX1W|CcLLP4JjJp?6dTEB9Ct^qf+|6@0?1cPkH_stQ7KicEIgWAbxnJE6)#6*T~ zyA`|QL&Cr1OEmme4gJ3ZyxG_PHxZ3wgFm@;r+Hfpq8A1Bt;h>nQB3)g~>%>oD zjswbDorlWRzDyo*CHwr#)=9#FgI3d)S)(7O`_{uJw~g(k7jm#`DCL@e5j@DlYD0hHQ>*H|<%Ro6v^gpB|*Jao}-aKSd#x76GN$Q8eX*0j-DqVtN9U%+7cbxb6xP8?{n?hI-8F3Ypt5* zcUM(`S99T1P{O0(*8I#)=B5`b*&<-iGj`MSv7J-VTdR{pWI9X6&%6HmeaiW-It8h< z8i&?vcaJhQg7f&Vcby>x{SlLJ&!+K?c${B@y?D6u{3oky-+SIeAFBpW8F&4?i;y!x zQ#&v7)Ta2v{Aq)IR3%P!xnV?Me#81_h|LIymtgmZW!H1YHlNVZw$m5mSizp;CwN}zPp9+`=28tXrP*qrQ!_&C8NeO zMOrw%{V1jN$`7acv@4xKzfSimC6FvAJ5;u*IyHiE2}sSBJ>#L;n$X!Gf-Qq9 zQ|}ZXN#K*_neP;l6X?jsn*}>9IwTz0X=aU_2~JX-0Go zCGWO;#AgoL=I?G*Yb;M_45OT!QX*uq$3t!sp+6$3j_H-CTzq$6D<3EK$=n{h%9<}v zTs!*?Sm!7Mz!gl!Dp6uFKb$G-tER26t?SL)L{44)iVISz`x!>D4#NJ%nuIkv*iJ+i zn_g_1AKM~!qDm{0YQxh_GU>HimBi(qggRb~i{rTt(cOxi4V#;0HOzY&1QW}($a@Rh zNNE!s#AIjC5KiO3iw|!MAv=X9ZEJ|fX7z#}<~Uj74V}$BL`0e^EX8oGQf)d}J_%%c z+4$3`SsTj>ihNY7FuCNpOCo340W|LJKJiN38Y=CH%vr8cAw}YlFgE-qg1Xw{EVb@s z(@mSTJP&tZoVD*%zT6oz-g;3{8+Df@D0MO@|61k*Zkabc(cW6HIvRj5ip$W!w$)}C z#!52ZB-9bxAo#jwnd~y<3coM7*3_NZ{^3Z!>Fw1#{A9A)a9ed>eyK}3o9rz^E_Si`4~oZ9J3NA z@;TG9BzsNA)!n>so^76%(<)|)Rp8PRURa+vi#3UXtxVv-J1X7j$b5Qx&SSSeMR_+5 zsn?IF@MG_Zbcv5)uUp5>glvolFw-10)74_-6&7VH%vUI5N}wsK1ZRKC=-klIXY=QqEC+b4JiKK)X=f5gxp#YRrf*oWr z;j!g;t`(jXJ@{n|gA?g-e!6mr6ZN=<`TV8BmwRKd{=+mBf(qY3+iFw>)ef0KM&bsq zQP05bVMzr5DYmDqVrzdWTA-FUcWP-u2>9W$Cl?F7TQV}_%=mfYd&)Q&?EAuFvi(vg z+JdJ%*@6HXG2Q2yKAFtvd`a_ZxWk=d$IPcOZviXX%jOxDLMcj9M(vp1LV2#6Q$af! z0d*$nl0(RP>5PFIw^wV{6cK2didLC>p;MK9%X7daPQU)8{5p>D7u>VdCYuzBsq!^cXS|_zk?n(%iK58 z-IylLzU%f^v0;Y2C-Wz0pTREUi@kNon_GqcGQEbc;?05m{mRcbDDg=pL7$VW!JweG zvEWG2;lyeo``6v%0zeg5Xa%2bWTcb%}OQVhSyT0rU z+O*wLmx(CY5@!%t>kZJ|+D#l@nO0j2qzP6?$Yrsikjt99IP5O5FM2$Oz8o}8rpvT! zm($-*I4OY$qVPL=vktA}8yV1oamasqRe1RP-l}Z##_FMk-`*b_Ki*v_h`8*8mOpU$ zozExGR@t1GiJ)b^PM8o@@~qX(4>@un)E@GMQE7T*d*u#>kIL6S3^4Q+R(M*j{RPD%$J!yK*16fO%I@aT2``FC9+2Ym_kl?i4D-`|2a1nk)Ai$r z?wP~+9}#VLU-lKa^FA@xU^te}d{MQ0718wJ>f$@82Xyhj5+o)pa}Lj9Jxa#nxyd-I ztDM(0JHwu-LTmk4-I8kN?1vd-zUU6b1H2kZ#TD%y2$<&b822Obz*-&f84Lc9fAmp~ z5Dfwk+7(9NsdhYut{2K@exe%H`Z&#BU|0@)`6W_9hNTR%=~Pw4Vz>vvVrO6+muKqC zs0=^hjFDo{Y0IN~_tUP1D!a-DcTVpSvO?(uO?P?R!sEsY>FK_(f;1Wn)$n5QrGuCG zou;|>qkRoNw?}}R0q}nG*LG_1+pp>gAJvMi1x%!~6qlmB2l);|j3)?5eFhcOp;eTE z8%`*wXEl|z={f76ubD7AluB${`gI9&*2`uUq`N9x8%9g;(+cE@;go9C%{6M=0(?o0 z!e}HAMpam!l+}QGbO|SfC8h$D^M$~yB7?=<*nt5!->|DEc_(%}eSI!3odnwxA0Kdd z6AI%r(ej)QNSP9JI*Jp5GK(5?Rg1}9_tnzbM~G4CKD8`^=fcn4(Zk9jl=svCsFO6v z`K(ZTr;M%N!SkjZd5sNY8dm@}s;N#fUa6uv9(93vbDyrb=~`i0k8efgm1jNAH>JJw zv|246rqz!9jgjXRsYG0T4VsFgB14doR~Ws44iRHVQ|o-T@74uxPvT{toTUn$B2xLB zRHVF%b>M4t>-Fs1TsKC)t-gq5mH|VXIg=qC2l)r|cGGh#;Fb@uzkLxf`|2kx@{*|$ zMkSw?rS%WC2n*g}%D_C8s;)%2Qn3*Q4v)~MEFAv1ALd4w*$-YWdN^-4GRkUf2ky`{ z7b%Ig>4OG!R%aE_PcS-i6qKfwdsHfflxr!w(%6?T!fxuRs%6Be_R0X|i&A{pMW!%fIaRh)f%*y;o>6Y-mspp&hV~<9? zfHATQ*j96>h3P)ZVM%FHp-Az8ap*UMRux4UnrDq`!i`{QD)@aE`gf(4w%lTsC$&gO!tl|mjN9?AX;#!Rr_ z3HiMc{8XS4^?Xz8keiheYJWpVQe)^{sDrz*$TU&5(Hicg-a$57-_ik(LQM;ZGJMkZ zd-{5`LyZ@Nf~og)Qnp=UgCLI14z<2y!X*{V7^@;lmD@hru8t5%2skU?cmL7Q_2;`$ zr(HcLc(;pZ}ev%HAUKdi>P_Rj0#>3&`5mrRx z7A@a@G+B0Qc{X|8fJg_cZinC~_7%QY-ICP!+ZP@$QK(N)(Cbn;%+Ud&d9K;Ii4>f< zwb25lk`g@PDC%3m)^%pyp{r_yT5iIaOzF(q>C2)~xJPhQI90$J_x?isJXEI=u{#QS zvHDzj5;ikAgfjWMP&RhtdNhbuO~%>g`!ha#CF10E$0N)L`d1QWsq%99i$=ng(!@gk zC~zZsaRqB;;R;7x#k_?a&!!`NdK7dBC}r=t=JZ_IC? z%7|@PdrgErJ?#-oV9}Dn>a>jZ@>T=i*D2Wj3>eHqkz(A1l00tw`E$Kjtc=|3oi2=` zoQnD$f5)cRd!?v~($Xvpb-ml;9l=65y4r%t4FC#7l;6&Nd336p`_Cpa2G7Ssbzi(8tG5mn!V2k8`tT*vpcuwf< zj=hF&^<-1vZ#YYo+z&Y`x~PmiD)B`A;lhQkgaOOK_Z;23&!1ad*q?T6zOy{eB!PfQ z#o(pO*=bogu$8q1+q3szo3PzhG2K)`Plx!Kw3{%b|cAIs+pZIlZjxaXIkr5^w zJc-4F14_7AM#m-l7vtUZ7GvU~cO;A5)}p&w74ryp8t0GdPjjD*o<0c6X7l-!fbngFbI1>#4z&C5uB&Lti#GVt!DD_B zjlz9-`I@(2SXMS)%e|K^5dlyl;w3oK7i5@4IIFZKciU6$~tm^org;rZZz@w z7IFRbV#{I|@;4M>00yQR)pH103fW9A$$_wZfKEkw)_6$bWg<5lG*OvWcvj`S;PPI7 zya%mC^$S{rAj1fpy1;^n%bEzsmJFDCQd z8|Iu8v44yLh}FGtO$$2WhnKG33Q-+UwE#!RH{%L~g_E_)_^p?8zho8!603=p#AidLX5% z6i3(8#B76u!w22b%Y^+SC?P-3p0#8X>ZkET>6Z$g0fQAuw4x3yzT%)zwB(5=1rW&q zf!l+Su{ipXBPAoNOkP-V2)m~mI@yAPN;Uo(B6qVze)iEVseTZ1EgE4mS>59(ikK+i z_}%nO6FMb#P5P#z(FYtc#+x~VObE|ld-QC%_#N_9M3FwHDuvv;j^$ro7XdP&yk$Nj zu3nyuM8JWCh>tm^C2KC{sB1=HfTQ-jaM)kgpy%C#3um}a{|O4AqeS5nPVq6njAfs#~!yzRrYh=ty6j1 z)qO_Y5A~Akdm#mvWBu?tqYZAk_7PPV_Y4moCu2`oA-B6)@O5|BC+eYU=+x^rKJ60> zw$GNl7lZNpv>_f+tw%`Kw0vmw-Obw+rP`)ReE7+21DjGEawL>a-i5bG{;vRu2Tc4Z zGAa|@Z5_=PJ+~cc0@>WVFLiigF)8(!#2zxV;${K+pPchGE`H1|7@{mLC8fHLg6JVF z(sMPLG`ub5M4j5<>~Z=cZ1uIP^!?BBy=5-jsKu$^QNS+*Nx;Rr_;arL2H(_=Z}j4B zQI~Kv%pkCZ$!&5g&!&4Ksa4-K9A#v{sWmT&cpR=Qtz+bElE<_kn#x&gy!MV;tR5O` z5sWlCI^Y}lfscL5*uA5%iYEF9n~^Sw;~GzZ;MWTL&&;0|V|UG$2kHMYqfnseL8CA` z2bEak)CyMy27pdfzSkX7rlXqZxt_wxQ*Lehy1(Xv3|SgxgB?u0)tU0rw<8TMmI2F* zD158=64!;# z{>5+M4L`O#fNJpj1Be{}1&P8h81x|iKm!dRKr^q-!CLqyW&Q6O2tRB}P=I8h)feG^ z7Dc=bjQ|4t`h#`!aR2Nn9R$8%pMi7y12q2QMsXl#+oR*U`&s?j)BjzQki^6Nh_c8L zKN`bZBX;ijhu~f1d=v9r1>U(n{DJ5TgT!S=_P192SK)>)D2mmm|3|by2>i3}2ujEw zjH2&gsX$AF2mMFCWZ7>WWB+f*AR+#*DE>>6{(mA0B4%okX7*Wun@(l|%6WltuM_DB z20`$jGgr8`Z=qJ9$_0Hk{qgSiW18cBFV=i+sWWfW`IS;@O5V>;O(q3$6EM~zy$CfZ zbc7{{m>X&xql@ttYdS<%axZ$dQn@gj`h8YCy|xWva#g=q;T>MN3$WTGa262szBHG})8# zrQ!m42+#;QtQHvcjF>8W&h!`T&Zd6P)@HzfkjARLGMg?Z~0wiMr*-LL}jYS+`{JB=YD(R9{v!V zH$zIq^Jc*1mda>V(H5FbE5B!)H=7#Nvyv%$*5N7m_{{zTcT2>k_ZU)I-nA- z{>Xrf72Gvx;mvMdDECUq3tXdCp-n^r4C;RRdiu1^X_K&9s+u-xF;kObG@L&C`=1Xs zP6Sra#taI46qKsaX{CU=+>0H!A!HXjd)?1g{C=)KEMH2Jk7u=1Gu~`kuG8o@qDaIW zW(;Crh71Iq0KR%g#AL`Xkms|`lN}I0wtAq>02g_|lKzmext+vi7f~>LFFV`zzwcp( zGgw378MQAPMnaFhI*peR9}I5Q_EL}M-4+z1NP2e~-nFm{Iwe*}hfS{z&zPoNTBufT zQAt!)G+NF{dau{@t4EPc`z|5z6Iu8yQ(?_6t>z2I#9_o#YYOgkIbq*5T#Xxd+1Ma%F0g=$BbWYGF`npo`V7VGbX50{%&ja= z*~uRTOSyzF97Eim4m=ZFY)0s>y>Nm^zxY{^1EGlGVQfM79iI>mS zp6tW^hJH8TMFxlUfPE)+XBfae4sp!-Nazn-ZuY3W6eLyom2utEA3;3GVjACG;@jI2 zR1FS4d2>8JUQam140@S@es73kEqTGybTWf; zy*6U01Dr>o=FnxF{uPghj?-p^&5$`I@os;e7wTC5e4}eVwX(?M(1IRk0Aa}k_$>CK zW6ZJ5Z@2YRaL|l*#r2}SyM^7;8J>%f`>|5$a}tAW5xb-_-X_s0J}eqV3@)ejPzS{8 zP`8MLjN1XHR^dKBc$Uj^{Y*N%i+tH&4LHzNST2F4zz){xS6$9VXS?ZwXOqBAZ#$FP z#P1(Ab|3CKl3JYE7Hw^Ovu)&$u>&>;!jFOb8z{qx>=Ed*XEpmGgeyL_m0j!@46dDD zJ3JRxd;ny!B#X6c79)1A7i}G)kNE4reK`jtI(4GX=FY`uO1aE9cr1q0Xfw_wq}$hn zc4YtKiK3IJ1DAs}x|DG*yxpxvu=#yi;IH@O_xRoIn0JCU79*a_PDxm6HnO6p38$B! zEBQsN&_S!6Cs~&Tg`XsHGLJ)r=lw;W0{M-A1Rl=vnEcKKZi7w zkM|+cRWU&N-|E*y+3(4wQFU71i|8D(5`Doha@g^J$hR!s+DkuoC7C&XS$8_>@^>lA ztMYGfbl;P@x|r;A8Fpa2fm0zw}qZ5!@k~ZGRj0^1ygx;cVzTf z?h;&NUo~2c%J+Edt?^Azy*I!4*!gbK#i4Lm4Zg!=X;&Z^Ez5H8%N($!yIP&3JkS0x z*OO?oS~w=0GV|oy<&t3KEsmKp|8av1lfXHDfL4+gL5ZSD)h#r@g$hnK>$e`FM*~h6 z>AJ`AcZa)&h3IdI3Vi$pXW9Co3bSd2VQd=_=xe*LZp^W#t2QyL1hF{VYxD~CnhnPW zaa{%nyf!IbGCRvohs4R2c`KEj_hT|G9yeZtZj%1r?}467MOlj0t9LM*g6cupbqIkI(#WkQp{X;H#IAAu+6l}ds@?*-yTnYie%l(_RTv{ zEm5Um?1jE@I_1Z16%miB4~bMVIb&21F!98+A2YGn&62Iv`pP(Gz9j(D?Pa$1d^QHG zfpQ>|J^EsQIZM6IW4GF_b426vhz}kDtbCPM`XQr#l>zaW!2$fR0ETR6_DnBd4487vwcS?JW2 zC}^?v%oL6ir9VE6YlDQT4z;qiXo82EjYP#{Z6g6bK0SwU99fuMnbX#__5R|^_n19` zyZdxq`k!LIM&QBVWon3*fzP9g4CgOU(bkV%y>}Dz_bXb*eJ(@vna_L=Em!W#AqJbO zKe9cAIIIxdn>J7ITv9=w_ju%Vyt^zGA&Xh|075AwuPrNwU2%PEy`$$Jhfmv|thdX4 zYP{5UUtvFf`}saE9sKdwCa};TFkqWf9_x4n8|BC5%be(r&LLYkFB$UW+Z|*d!Z*oh zAM&>m4*3S`?N+7fP%b0rxz3ho?0$n|8r$*X_lP>@n zJ4vlBaW*D0aeLi!M9)9vvThRiO6AczGce+iQn+D_oz_(Wj7d0xweoi1;c>!vxwQBVXLBwF^F_g8C* zU!;T38f)kAxO2sXohjy-uB3$Ml=8)e7GD%p8>1$%@920Rgz^Qh!MONdS89(=F?Uq4 zP%<-}y5QKP%1vFrSwUHg$JCiCNl)(cjyAGVrT`llK5;cTzld9clXNU?RG#UN*8&cp zqkkn(PBdsD;LygjAL%!UfGdmNO7T##?vt<4@9#hzDs+U zM-UEoaJ6K@{3$p$9e6bu{gH;YGs|@4)zZxwzQE#MA}cvM*$By_u+sD55PJritS7vh zLEXG@U+?J_w{mDmusb z<+)9+hj82Wq3bZXJMWEUew|@-MBEZja(Pif6matWKzS(dejGSIO=$F39g_!lp#^LC zwOY>jMJS1QrtMPT&h+kMbaj#d{=RZryF>_Rm<3Ilx-hW zGY|sowaZE+lx~_A+Vt&0g>1wsz3p_Y!w^j|^YpU&qMzX;(tr7f3hUL*$HB==cukjBMtSQ85mIY}hjT-2 z7hoG~k=|qo?-!yzUEJrJ7Y?uW1dQ)EO-}mwlk-P3A2qS|yHg zb+O1%zH_IeH7+m^>y#x5zLIz(_>nCW_g5*Y^CzqF=Dqw4!KvZtIjwSgsO>m7c%>0^ z>QC**`|%ayPZs+GX)2lKFHPHUVHUwG2Z@EYu^bs|CI$ zpZC^mPDE4IWu8i^g4xd7>8$3Kp-%-3sBYXv6^6Njd+#hTr+F@G;r;}Jue$&j=aZ7b zEHleyf;2xz4?L+q_DU?j?^ldEhwyG?w4*Bn5YSc;G~;Y8%^kR7HfDRB4txIWXz@r@=(^X5Ym9?V(hKsVEf$5IC0pZkJ4i66<63wGg6biSoCXDEV zYL&v;2&^{J7AB-&)fP~0xh~WiVjT6R=kv@%!J2oP?+v|bHrYMe3Kr91h(X}l1dEDy z+3wHaWdYBfBIUqB`yhx{D8bOG6q4Q##(WC(aWfR2KGnepuW3iE9c!~L-a(WwlJ73Q zB$ppkL=hLs5>$4 zlZwij9`maJ|8BkjSpc|k`9S6%>E**xeN4SI?scX$RP6oAZ+)_Hd3jCb>R{dXu`$1X4%Qmr?ygGeUrm{v|(EE>>=IMn4!PEMDLR1TwRFC_Ra zcVB|cqFZM8>CXKc{RugFi-nhrZcKG68JO=;1oP$X6&XmV+yLJ`%HjCK3LFLF%B%~E zWR#)Yth&gB?jV*ySD`jTS<@Fdok)oqT~lV^BsDVbdD5RnVsED_)wn96(O{!-!|0GS zi{=@0*ZBQhPyX`K%xV<3k$$iwys$)3zI^Ofnx7tgHS~O<9H|S_$Lp9eZC=uGmeHMDx@61uB=>`-dVz&O1XH4Qds> zKJu|2PzJadbmbO=9m@T`5~?M0p1v>Vwzl^NBoODz-g~x$eSVlJVQ29ib9Lb`CA@xoJgQcK9b~>zsN56S*64QT$ZL5`IS?0dzGN$HHCL3WJiE@2 zBush1;Y?6`;|pj7Zd)18Hchh8>>W=8knU$^;jfq?zaU#T!Sv}BcrH$xVClD%e^;1B zjK}o!4DeM0S?_i;L$wCu0}@o5>Mg)ZH*ez8o-MZs7m zRy@*Of@eux&>$O0Cp${B8c*alB5a=ncpYB<6Z3G)M_Px7ioh4Yr4>2_(fcZwc3#JhE-*H^1Q%j@oznLv+GLh)TpcAv{TP#-i{?!Q~4blYP)3ji+&irS4%lxx=4b;+_Tg?l8X$f zz>i&5z(?g>{k`pJqd9zb;daNYiQF(-Dt!&ZK4}Rmkz>tcX+h4>1&Re*AgsK&cdQA1 znQ?bKMn7^O5(L>jbp(CcI?UK=G(n_aMr8@KI(0E<4@|S&lHQ?ki7GUSoT?-%NZ;7c zt;M=q(9{)~!v+^V<%{}&(>gn-2(9G291ChLa~GKRy6+JLGJ`epG+N-t(?9}X>lW1D zT@$s+B!%nEpv7XZ`7mQA@n=8Qd+vU75QIvjX5ACrsbg|mQ4~MvHgz0F%Ky~Nf+#II z()&6Dv*L;3HA@c@7Oh6ULYp!!?U?gHU#5xvYUp(&LK|=Q7ooCxQqNPlup7uM#)g2b zBGIQ&6bUfcCKwRmu*W4=?Yp@IOJ^;1oBXLjabCumJ`$5B>knyI@{h>vzc=5PrjgOj ziuG~JkAknUWHDH2dT18W>)dlaUCGN3LEeGMBvVAJLuIy#uq6E*YS+Q&>co&QQExAVe{sAC&0 z>6Pgpb`sH@*cN&zgZ)gRI$5)lH5F|r;5y&t0C$+2-TX9HQ>V3aVO%VgAD)u{iQ30c zVpX;8y8J06x#(=628_d!pi0z{;h1NqVtKDx%V#iVP$znI>561zV6Rb&FTQH4Vwt78 zi4bVrsj!XoaIse0=zask^GhX|p0;LLsOs9XSDA-;pl^ZRx-`1oTXeY=g~mz;6#Gpx zX>?*o{*G~7?ePMmBg$)c%~ZQL2pYly1@VkP|H|;VL!xQ2b{h=kvJ7f1Gvtheq)#99 z?4)&*0OA(UMyn0meJ?*63Mmnp3R(t40!Vx60p#=R8Il2az6$1JJ<)3}kj zOWjb(H^VQOmtlQ)?r7mwEd8GH&}UN66#TU5jMR)U7oDR-Yj6iXnwll(P75v8qhN;W zG)9PzGG^nxn6;6GoQH6d`TSCfhz?9tq9+3LA}@##Q&(`~4YTx-PiI2eKHvTgyl_bh zcM?gO{RQcEDwVn7HM9@xmrmNbzX0q--X`z##eY@7>RO7_#4&ys3lK;08njV9cWgNx ztxVx_og&T6=q@KMl@qee=eB{;!w178GDL;ceGP?sg!8QKpmrGe9tD2v=1t_bRS*%X z7kd7yV<6b{W$FRP;zJi-UQHci`uB>*yl{j}&t7(nV-w^rWG!Sr=@bdpV1s-K=U&D^ zUb6eyc>1D^fX%+UbnhLcABAo7NIY?X=q^7LL!kOhz?t2n<01GZzQ*el1)A$t3Z+)6 zy&ZqmDd`UTPP6GUj85heb!ds&P3!;#4j3Kut(!{k4?@6~k@a~m=)0S|5xCE2;O8q* z8~9?k1#>W_ndHh8+-{F`8X?~jBquMcI?%lW-f>&c3%GQIvfO~gJmpc)s&fhu@+7w1 z5~r!1;NCOQx(H&cWSv0mf+UHil?kB0LSgdw}KQhN#R5286$vm|}&Ktnpc6ZB~! z*H9>UX;DZdRz1aA%BITqM{5xc&GaH`N9d38Pv?|#87Kc-Z14zR-lXu=I$ve%>Ep z>D;upFaX8G;~(t?PMgNtGR_)p767l16CfVnBsxdd0I41Ym5c`7-;o&sSO2@;w7rF2a z?!QuH6h%RG^n_KqE@q_laF|Tos%QK}(UEEBG%l$-!{i=IvlQiDqAX3c#2Y#2?RrG* z#ftEP{0ETU{*2rUNs~y6_5%us#VDpnEd28mB7T8mO7=F0#4ny)kb%SPJ)%3O-pEu- zy~WNXK5xFvVvYV%H{^eIqhB&P@~xuASOewUEMkOWB=|%6?U#u(={R!4F=dfLg}?I( z``|!f>5RBvozHam%Z&glt;T_Hj%t*@zI_Vxnr|%99zKm)3|ZKxb26D2AsyH3lIRtg zH;V+yZm@v-CU76~9am5>I2w<9Jhjm_^|kkIo%?C!uezyFT~Kb>eqLv*N+tT9i`0sBvndPYeSm<%E}KF2Pgg!YgWeApq4;t z^>H|hIlz3cA@6lW_cfF0iF>9X)<1=H)%hyP>5S@xHi0!9MNY0H!ac%doW$qaUD&=* zp0bEkY?^G(CuC$-_942IB#6x=1F0yDaGK$3yZf@$2JBx;kOP}8;H7zcwoJ+EY?(Ak zA!m!I88j?qdqR}_X}xicEgU5-=YA}OUs{vRf&`T2!p<$9!ADJ>#+k6lYMQ*wvCgSG z$vVjY`oZg!U!%^}&vLy%cAht(z+eY9H0w>hi9UiZ4wv9h<2*>*#fCmQv{}zJ6A!-y z_Q)O=-RjO?lmuK50oyI&lBuocX?Ow-6Q04PYjq9uyH~oL;Ru0hm1YY0t>7vC@vKIk zC7$MP*+M#l<2IWOonsHre-)zI?n7d}p0~5vEU7OEEH0_LDJb21WY4#5ubRWhTdp;Y z_7pf|{rx%7<1AsVvXi);u-H(bcgYB&Op!1O-NK7TrKN6It=EJ!Z7;vK#+mvK}z}}7{7NtfrVMfz?xo;Xj z0v)PeU8Id?M}F;H6?fZ686e=p!=us(zZStmmq{vwZ}5Z|svZ^jLs=vyFqc#*{&nPm z%j<0qMAn$k5Gqw0sLvm*b6VTd{C>TCS6p=J7~N<|enYTK_DaA@L912~L%{z^Ihsh% z{(uZ-(^Ck5)bEKs3vT(+y6aL7|4OV(t?&WY&7ntk=w@-HsSQR_l1G8%`L5wdoil$hDzSN0JpPe^iTHp!q>`b1etZzgu-0JTv9?y>xf3G|=@~ziLRNs3(Pe~}*eOBK+$bxu} z$0Zhy>2M30ALY7F<51Jj3|<#G2Aw9os9zOVW>m(5>mG5bY*rLf*WU7PHoz|wb)j_b zcez%>kRQa;?pO-zUR_~NBxk7$H}02b&b!GOJR@?t9+9bZML>s_T8 zn2;x8I`R2%!-eQq8)KG??rR7&lfzNG3A`HbP}8sD@AiQ&N{h7yq}$#PD(9eC5&vq1}8vY_rc1BZ|Oy4Rg_ zKrq5?7-d#m-=l37Dl<6Q?iM7iZ1Y#)>D?g52fuPBZTXH~XuljycTGTMY(Q z5wnQV=ASX^djCU;vO)4we4gUgt1i(<7O*7MUW^%wUjm7blzCt}H|&a{lB$;ze0eSR zBC+FEua0!DpCSYAZM&V0hvXcE$4%E+p+!5d!oAYsR(sW>M#VjRL%yD=ROTAP5_ru_ z7|DWiv=RKi6fNKL0d7DE604cLR4wfqr8h(>gC}~p!AfiQU@Gr&OPXIs=b8FTQ4C)x zV}@6?2dAOc(amT12gm9CI64g$N+g%Jeb$AV{5sCbVlrmNZTH+H%W{_v4h1Lq_yI?D z=}?){`0jMoUYW2%#O))uL6b}xhf0f!kMKth>)+{v`0Jn@)Vy%*S=6GT7MRZnLZTB| z@0z2+JqxVutZ3tz-E=BnvPF$fCJQa*rO%ew$xAi8vlYCGh}%~$SIR2WqpRQ+Z{)Bl zby!awMqN&0O=%L@-H^dO`>i#j@bunJGbTt|UAG1$?hxn|Z_}3!vBZ~ltum)2fp1hckgj>3QqD?NNSR#oeNm#Vi4*k`2N9uMgQ~`FkSggn_@s4^Hh22ZyqForgg0 z`qFwmwVzD%c)gvyF)r&k2W{5xxRARFtlQm{nm=Q->jq^`7??GO$Y<{0 zVENoPFnRUyJQ4uTLjiv$8MT=|0ToCL(I?gW!?SV<~5=>`5Xsx`KK`rftbw!$8BnH&?ih+xRR} z4O|8U=wrKhkd0C8VzEz=NS9}jQt-0R<(@f=?tj`uaA`i`6JrttJ)6^6aCR(HEYyA# zqK}~HROf5Z#>2 z9Lr6s!kyFa0IH=b-e$q%LwVx9o$>CU!(}WleJ08un4wb-%XnB*ywZ0{bab2o zRScoSv;?Tj-q#0{&(u^JdZLc`?s8##JL=#yn}q2U!On8#7512VeKE z4Ore9EXJihhUf=Y$RphctN|GzgJ_=nF!C?qIp*TMa)ihS*p~}U=lNpC0@{r-sjt7| zI+iCq=&+y7vvbF%Kb%pf5+0kai4=m@j(W;y$9z{F&PsetQ9~CU%1L$4;4le2SZ=UT zY{1ucq1Fv81E-sh3!q2>N)A;Dsow{Ps`P_>CLa(@GWx=brF9`Qc_{OEnUkJl#k3e< zH2K;gZZufO+5&Pg&^de$QC{}Gg4UOpBJj$jeS-0r8e(mzt&*r2Hg<7#WQ;&yY>>zA z^YGY5#Se0ZtLjg|Ju=S1F<`uQXe3YpUg3F{7knS260;fMY;StCY3b>513W#D&I$>| zUS1F;C?_EC0p6w&{6!RUq>gfRADULzq=dJUf!Zsg+TY)dgq^Y5sLD%k2uyi~9Q(X##)zdv~*pc?jlU~uUk@$78s zMt+FjR6lx|;n$QRKzQ(iEE>Js{*p6w>%PO@ln2A!(3~G|S3|5;j9Fpkp-v`reF4PC zCA1ab1?kQA91V#sQBy#Ntfz2#B351if%H$I94R(B8=$UUmia|vtIeMk7FxGMn@q6v z0P=3bervlCLjBr3V74_^tctwGTth{8I2hv+&~624Z?OFl9>xzgUv;0p{=V*^kKz3u zpE3Ts;bdH}OJB(BK2G=Tv;8eB3qShV$)_X7 zD?+?9Vu$H*&ag;Dj_BU=ls+DG!5>)bzqO;8?sg?N+K6meLB~8EI|ets?WU)~0SlRH z?XCWY|5Jynx7}oVy0g%%VL69<6Rd7ES4T=k6iHjhaO~~*7~nC8kzqrI8HfyHdrT}k z5Kyv|+tU!M_ckyzTKb?P`z^q%{f6p+wk&Wz7+%;^+8KV-`#Fz#04YJ5zh6|(Y^n$^ z8l4(TGY;T0fG9<;p8OWNJ_#Y*UY*yOqlHmS6-fQS$ZNc8CE=z^Hu6}PIcf;!?txI( zNR4|p{I&E)Btbg2H!#*9QKD6&ifSolmRTNYJHrpA`(wrMfNAW!Rb_Pnb|oh?JdS}h z+tv_mY(cHnDmEdL(#gKum-cxWX?SiLT{KF$z0=0B@qL5yD1W5s3BB(_0%H{H~NC1{)zUZ-x?tzIFh>xnUqp_wTc{Zdid>$SW%pufO-d2|C9vI z{-*Ap0Ep-T;xL=&9*#WT+OKI_pMQ4k5{#2E8O!PWyxI*u2_)(J`7sqCi6zr|<#|#4I@cljOt{=aKc6X}3)UINbSwM2R@DX#Q6x7qySDYkbl3r)Dx9hS_`fKu1J*O<9r$h^gxb0q*_v2ht0solnJOn4%hYJc2&aLseG~ zE^E0fUiE$i6aH@;H^6oN*qt8{bKQ)Zuce2Qn6sVR3*E!9u(8}8nuV+s(#~HVZw_Ab zj%JD5YRm^Y`h^g32yRanGWw(9w-np1zTYTRDebvD#wQaBcr5QQ9ml8>3othCGAX3V z`8u)12#oQFD-m>espT`WLI_x9?vcvoVKkxQQh~Nu3|Y~+-M6#;jr5lUemQTJ&04#O z@YvOn@N{G(vx$Q3+DvF7v&)3(*4?~9c+%muK?VX!=(Wsr5=V$l)pE?{`p^1f1-pY! zUf$x#bn*F_(G9o4${aMwERED?P{OBu^2@F)iICglXNzb!We?kYEQ23JCjvX}OUVR{ zIK(D9OLdZN16~L zP24zE`70b2+qmuhg!#nIqbQf$E4~Xn`V)+ipo{emQZe{SMf_)~z zI!obfg9HChLO=in{ZsR7o6&R|=V`l(JSkOZVb*BxW#+br0 z-0?|cz1HW9tua=$kLW)!eC3Gx2;cvOA@pPKwfXKNXZLoDGeu*x{_$TMp&>+4UECbx z-nX+JCttbWSXOA(xlF7bE6D;3;o8z{X@8i(9cs~2Kq!c6vcmXM9?l*c(1mK&7}>uC zm@%grTcX#w&XJFXEfz&-HV45eFFPwnHNWP9HxCLDda+*iyED`4Yx~y#%xKJ4J6TI z6O)XKX@9*gmf@B~?xTyK_LXT8j@_N350|~_$PWkHZYN-8n^Tjrm)jwa!%3cei1yB5 z1vgZRi&Dcs=;``Gk=lClLo3Pbz8G?qlDW|aFk1exs@CQ1l)aBQa*?pr$rf!qGuURR zb11_f4SZybvt9{g(*>5!KzTCPT`ao}gnl2LfZ}puy^DU)16r;Aegl%R{am3yky@!o zd#YE;veH5KhH^CtpLOzmTyxm4eXQ_j?D#R@U z(TZZq6ze@&?;ohIHszrMl(uAmtno&fp-BEfX?Tutvq#oNd$~g-q>HltHmAyLN?U6( z;j3u}m*ezkcBBwJ9k2zHL!NO+obAri7AluSaE3qfPi#$VePzH)XfY|I+2Axn$LhHo z%Fe?murZKA&T_JK@fnAb%xa(m3Lk>x?x*0_UjKPi{<|eHl%e8&}l)Au@CMv~TkK)GqSR#e& z^JR$C!E*PCkyKuKbW*F0QUgSiP||3au;8(q7o2aeUA3FD;ry+qVIvKv9CEC~zV6Xg zAyN?TkVtfXr)j=gwQDMOJ_D;oU)-D6#4V!R*A^yeLD*}JL&JUWh(~=c?g}@nfPSK` z;b)O^eIbWYe}Nh(HEEnAKl#e1hmNz|vvtH^!)a9bGr8>5Nlw@z#`9RI0!)6U6AA*Q z>9&tO-zXCZz~<%539YBHy)g{Q^4J{)CkC}=3fif5ckVZS@@+Khz3as^Q5&TepRpgE zi_EZA=uc@+yB|f%lUeAQ#c%*wXL6JY-h&&e^>ZG#Hicd*<&WdLT9KvaY zRr@1(T%S1iP(7nHT$p1VWI9hB1@|Ez38Iu$taX#DH_7B3Z}b~S4LHnB7BesHdMNPE zYiT=Y$7xPg7thvMGIhURzO(Caa;`gj<%4qWcbM991QuYJ=POkww`#r&ph3MHKyc5V zJT0#oOmuszU-yy4w?nDb13tbu=+TVhvi^@57SGcg$E&I$wPtlZ##;fxLhK0Drz}CdCYRQa&sE$nuT&V4OhRT+o+dD!GsD&%(V5MiSW1+6C%7F zS+iM%?JYQun>&|&2Lu21Ea!KbX(U60BtS0g?F0oL&ld(;ZDfs_CB%_0O|S{_&=La~ zG{CsgIQs>&qFoh7t)=+&rYO_y*Voz1H@T_O9R+cmlGI9BM~3e$EK@X)9Pes)xRVY8>nG&kq86E0SWl6pbKK;Iu7G2_1%m8w+k(3n<7hnd6)1H89uQgM@1XwQv@a9;b1JYh`T(Jao8 z6%~prVMTpO$8grbSrEKxtp&d!eN$a*5Q7RwJN{Nnas)EFKe)AMXHJVTpSrlyiHHeq zz0tKdfnG{waxcJ(sASq-nq}^G(@t5vYj@<0=;zG$=H>v$?s!0+t||w*_qcEOA&}?K zCRq(^5_5{D0ZisMZf_tXT7szIz_Ti+>G=%Tal#Ps*K~-=OZrHF>ho!HIK6!Ka{Z9j zj$Y+p3QuE0SWFDb=`#w59NdQIbtvn|gNLegyRinQr{3Jq0hUNMvo|I`^1f&)o85`H z^ZTRl;RbB#xb?1igTeDN)e6^NIFByZhO<7D>mLm@`VN)@N#6+29@F7jdV`HNLKs8L z(k$3#&NREz0SbR5A-Ew6~$^i!=M|Huo4(Qx|sxlr<{ zVf|?2o1aMwnMQ2R%iaYWZ#75Kq%I=KJ@;*%03NgBa$yCV!w}eV^P#xzzg^f(y6)Y! z_i>4HfKZhkG~G-HUP71jOw=p#dJX1p4<5#nQ;D%vy(lDZ9|eOmQ>KaxU5>Fz%%Cq^ zRgdGMT@|P6viiITr9!%P5@?WH)&SENUOB6+|2l}b{XSTtTvl-sS>X#I(xl?EBv|8I zDGsf9P4!)wslp^>cEa^M@#+b+Z*Sjfu?U}w5lk-heg=wQ(E0&Imk{0_&ljqGz+g}o z(Pq4eii<`oCxbU|H|g=X+5*T0_SmhlRh{EDN+|FN!4$jMXB%U`{uj7qvGfU&XiAtV zAD~%=_2=3whhi;YQ|g%-i$V@?>!aYh5{$14ZiS^cr&Br<#b%o_My`8v9PhhHmCi7w zI{Ml@Z3%uyp`lkiQymI7(_~^`GJS+%a(l5F2FV=k1mbM)&jCp=dn6Z0Z+0)(TI27m z1!z-FwO9LMxyAKy2=PnIE!8T|z2x6UoK?yMm74i+(OP2#e#;nEUicYQ=;&@AX7{H0PKBAy-5cAxnqWMP|neG31_9)!n_Q1y0kAh zO%uJWz+@kwtPR9>8qTCgSr*NH?AP(UHR0W`qbAy0b=VE(8roY?|QaX zqmXT?K~+55bEj! zDe??&>cYX_4)mBE_lV=U!3m3R>W4Rh$W#0~FiCrY&pK+hWY6@|FGFx*LlP;OrMyx^ znks1feW_I|+VO2B_Vj*?*iu0bUc?CP)Xy=fWo6zhaLvTTs|dJAzUZ>VutEK9wg~Ht z#_jd@qT*ss;b{0^8B7 z>O(k*rjCYI%h6LxNS3L%HEKj+);QQ5SAA&n%n96 zvCPtVO}4eik20Z8meDQ}tHE<^N9}S^h%M_XFttal0F$Q_wN1k`v?rf^gJc4{$Du?< zu`NvEjndT1n#_8x-5kejTC^&|BH+(iFo|;4HKn#I86sN;aX3j{MH+?voUf ze~EHC+-S86ImgG_jOh+?^Y7oK;cEFj;UMD8Fa4#QY!N)Y1mRFzTDGnSbXWrjSg zT1oTC8Z7+UpYaaa#-KjR<7D!CGgO<+z%aYPspPfi7zo~HWB-&6 zkL_rarNq#h($|b-G7i-CP;*xo-MiyNb0X+SyHjj1ezMO_alReX>w$b<+NUKxiuumi zQkw}(7kmN4fPA(`K4nYDj~*VXgxfdsyB2|@>;Oj-uyW%h1&aJsO3It=9d=?qkrv3P z%!1Xk6Ja#_ngnhkohzsJ*c4A}B5dQp{ol?etXkf$lO~Gh6g!`*j|M-&Ap8&wBGWA%b`=UpFpvwAfy~4Y^CtgFnx((sj%q(e zN-xJLB)SkI9O9~Ppo^nYlPG(G@?{fryDh$i#owW{^E?^CfDtq!X3*W+b_-prf3hp> zJbATRf}|X8JnM)_C9lfp*W&$i@mW>tFi|O2UpPB0ZnWfqP@bLYc9HPLj0FLC^*}n| z3QU_i5Ynb=%$0UBA)!d=EzGx~#X$j4y6jooslpw?mXjubpJ$taT7&67l z0)k!wqfTqia+U8SYW*dl!7)Q#ib~6@*|ZX7@ZJD3b;+e|WSN#p{Pca$CuXNc z?CCQh`A$i8tntpuaLZbH;FjvgWd4K&fyu?5g-wHmPM8hRJwEmoBae64JhIiUY}&;| zijEHELOBD{eE7H@brF2yOx6yE(Asq%QC0tE4BCMc*MybaA($F{I zt@LyFP^JS%hH$`&C5++C6|Z`2N+8HB)LWpL9aKsu)6klR&^tckd$)jq^Rce~GHSUq z09hMBqe9VSx=ydTh1LW}uRX~Fgq}Z!POO&R^rQqI^SnZIDa26b&rFkZm zct+31RIWN^Sb^x&c}xb`$qo5`dF^yi^7qSMguta3^y(0*E9$(I_zQ?DGU!Uv9J490 z7wVDo^9RL!oU#>EAVlJdglu{8y;<$=Z+RdfmVERIc=_*=`-hMIfZ4Nd^u0MH%oy>{ zhd&zTbDiy^_V!%oo1o1b6n2G&7jlfHhWxS{v_=%_+x!{8=N z4cM~egW~-E$o&58DPSG>Zx?t_L_`gS=Ur7oX^w(EZ2zAFxFK zZ7Te|L15-vz$qdZ0h^f5enUA&cyo1Y{kQ_=l=QF93YRwyyMp-2x6l5unHRj+PS8J= z_CGewdw}=fa=_|(`}p60V9)%31L4Z+f4}$p84$ESb^Z(IeYI$qu@wHV{`_6%zkvTc z6#w^CK}Lc2djWZWrHnB8;rG=FK?BnO0r1M9{=RM0b-$8HVdiJOcAo#e(!8Cd08-Aq z=cdj12WdtI2r4OE-F^DC`wJB?M}ctIt(xxSVaY$QM8GJyAL98RdtrqM0CQM(?EIH! z#^1Y&6d>^aWlJyqz{(A?^uKWAWla8G+w&a=7Q+k^Z|=|QMcn|HuGOK<>_6zfA1t_K zhmY&u`0gJLzXD*K<}RD;{ul;KU?U5vTK{=h=mpHLC7dfY?$2{b5cIK!;Xj=P*&wLL zQj^XXs>EJ1gQF`|N_qg}mf?8NM1|>aOV87R%D<=$h!99!fQufXh6wBSHzc^^0>y~W zvaC^S;*mWn8a4gH1(@05DN>v&h0jPBQm5|ISy|%)>f~Pxzl62neph=07ygv`L&D*7 z!|72kV=Je$MAGXu4$WCqa0r-M=lhAx&YUwr>lORuhY3Oxg^WCyH5=xfx1d?~GwJ#V z^m2XvQFvcut3$6wutzlZ^j-{UC`6*yTn#++IqK-jhs5x70bYjZFOPMQcMmr5Wmk~7 zICtvrIRRGEZq&M z3Zun`(!+FeDMic^RfbH|Kwz$$kH5+crIRJ_9L*Rfj@G}7#W9G-Crz(I!Yrg4-oO3r z`UucTP$Y~Azy`_QFE!+s572%bCA*Rdz<52s}Q&NP{1L3E_b6v3c^ z+33326u7fpNFnzJH2sr%qkZ6GD+Qfxd|I+i*3svNVKh<^O8?b-SLAqhovQ*X&yodkzi7Tq1eK#cl7|mzKEK6Bp_I z@^@*RqugwvG5+qkQ$kZcyB8ZC-+12FW!7p}JLazF*rIXU?7rahV>UCf!8mHB*namz zb!@8a_U)2C3_kK>Y7j9x$}cCE94BE~y9I*SeKt69l)-}%PA-2EL! zAQO{fiR@rByghqkIg7DM$4>)r6FI*ZfO?n=M9hPe7zOLGZZ$vJn^{PhEj?~^>h8tH z6lDG9CEEI8SJ%;H_J05ERBg<;L$myu#q11X-UM6A?Ut$g_g{L=Gpu>TAT907x?Ml^ zv#x2Im0#&LS(ax%!V1~^r037&;H@7#K(G-P7Er8jJmOmbFnK-j)k{V{Pd=^hq z6LyROLyzpOB9lb8vU4|?&-g^N@B1mF|JzNnC5!(=p;;nz6|I=kTLVWtEh!rSH5%WgEz;DplN*Q;Zov{46 zDhfSPMS{JI`AewnocJ`ofa3QrSfd`oZ6Ww&+P$jOh5i4&1mOx3Gwe!EDeCt;-M8Hi zu0y`zi`wgdRdh#27IV)l5QhP|uglN*aG7ephwkg&B@euL0r0$v6J)<}{eBIU>}4Ho zX^1EFuZko9c#;BB?7tcSv?M}?|E&Z&GaAJ|71J;tF6Ut=vB5^}ue$FjLB!ca@rXEo zmwKSk z=nE8??LU|R1pIzp*0||;kN(qL*x(`hWuCkm|EIq&h#m5N8HE2vkB=EP8ZHO_uOeY! z?687N|LF)QLL2BWpCJ0biZDq*#3EGxZPb59>i@W=xZzm1Sj?H;7VSL%{=`LOgi8c< Gef|g0c; -const c = React.createElement(Cookie-consent, props); +const a = PrivacyManager(props); +const b = ; +const c = React.createElement(PrivacyManager, props); ``` All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. @@ -36,8 +39,8 @@ All `x-` components are designed to be compatible with a variety of runtimes, no ### Properties -Feature | Type | Notes ------------------|--------|---------------------------- -`propertyName1` | String | -`propertyName2` | String | -`propertyName2` | String | +Feature | Type | Notes +-----------------|----------|---------------------------- +`consent` | boolean | Any existing preference expressed by the user +`referrer` | string | Used to provide a link back to the referring app's home page +`legislation` | string[] | An array of the applicable legislation IDs diff --git a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx index e3f1b009d..adea6406c 100644 --- a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx +++ b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx @@ -4,7 +4,7 @@ const { Response } = require('node-fetch'); import { BasePrivacyManager, PrivacyManager } from '../privacy-manager'; -describe('x-cookieconsent', () => { +describe('x-privacy-manager', () => { describe('initial state', () => { it('defaults to "Allow"', () => { const subject = mount(); From 3a3a3748c18026ec4f0ed0ff99e39f190f0a046a Mon Sep 17 00:00:00 2001 From: Oliver Turner Date: Tue, 19 May 2020 08:37:38 +0100 Subject: [PATCH 397/760] Remove package-lock.json --- .../x-privacy-manager/package-lock.json | 11476 ---------------- 1 file changed, 11476 deletions(-) delete mode 100644 components/x-privacy-manager/package-lock.json diff --git a/components/x-privacy-manager/package-lock.json b/components/x-privacy-manager/package-lock.json deleted file mode 100644 index 5bf354b22..000000000 --- a/components/x-privacy-manager/package-lock.json +++ /dev/null @@ -1,11476 +0,0 @@ -{ - "name": "@financial-times/x-privacy-manager", - "version": "0.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@financial-times/x-engine": { - "version": "file:../../packages/x-engine", - "requires": { - "assign-deep": "^1.0.0" - }, - "dependencies": { - "assign-deep": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", - "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", - "requires": { - "assign-symbols": "^2.0.2" - } - }, - "assign-symbols": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", - "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" - } - } - }, - "@financial-times/x-interaction": { - "version": "file:../x-interaction", - "requires": { - "@financial-times/x-engine": "file:../../packages/x-engine", - "@quarterto/short-id": "^1.1.0" - }, - "dependencies": { - "@financial-times/x-engine": { - "version": "file:../../packages/x-engine", - "requires": { - "assign-deep": "^1.0.0" - }, - "dependencies": { - "assign-deep": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", - "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", - "requires": { - "assign-symbols": "^2.0.2" - } - }, - "assign-symbols": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", - "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" - } - } - }, - "@financial-times/x-rollup": { - "requires": { - "@babel/core": "^7.6.4", - "@babel/plugin-external-helpers": "^7.2.0", - "@financial-times/x-babel-config": "file:../../packages/x-babel-config", - "chalk": "^2.4.2", - "log-symbols": "^3.0.0", - "rollup": "^1.23.0", - "rollup-plugin-babel": "^4.3.2", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-postcss": "^2.0.2" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/core": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", - "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.6", - "@babel/parser": "^7.9.6", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/generator": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", - "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", - "requires": { - "@babel/types": "^7.9.6", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" - }, - "@babel/helper-replace-supers": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", - "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6" - } - }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" - }, - "@babel/helpers": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", - "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", - "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6" - } - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==" - }, - "@babel/plugin-external-helpers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.8.3.tgz", - "integrity": "sha512-mx0WXDDiIl5DwzMtzWGRSPugXi9BxROS05GQrhLNbEamhBiicgn994ibwkyiBH+6png7bm/yA7AUsvHyCXi4Vw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/traverse": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", - "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", - "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", - "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "@financial-times/x-babel-config": { - "version": "file:../../packages/x-babel-config", - "requires": { - "@babel/plugin-transform-react-jsx": "^7.3.0", - "@babel/preset-env": "^7.4.3", - "babel-jest": "^24.0.0", - "fast-async": "^7.0.6" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/compat-data": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.6.tgz", - "integrity": "sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g==", - "requires": { - "browserslist": "^4.11.1", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, - "@babel/core": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", - "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.6", - "@babel/parser": "^7.9.6", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/generator": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", - "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", - "requires": { - "@babel/types": "^7.9.6", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-builder-react-jsx": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz", - "integrity": "sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/types": "^7.9.0" - } - }, - "@babel/helper-builder-react-jsx-experimental": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.5.tgz", - "integrity": "sha512-HAagjAC93tk748jcXpZ7oYRZH485RCq/+yEv9SIWezHRPv9moZArTnkUNciUNzvwHUABmiWKlcxJvMcu59UwTg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-module-imports": "^7.8.3", - "@babel/types": "^7.9.5" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz", - "integrity": "sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw==", - "requires": { - "@babel/compat-data": "^7.9.6", - "browserslist": "^4.11.1", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.7.0" - } - }, - "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", - "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" - }, - "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", - "requires": { - "lodash": "^4.17.13" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-replace-supers": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", - "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6" - } - }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" - }, - "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helpers": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", - "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", - "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6" - } - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==" - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz", - "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.9.5" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", - "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz", - "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz", - "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", - "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz", - "integrity": "sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw==", - "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz", - "integrity": "sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ==", - "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz", - "integrity": "sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg==", - "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", - "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", - "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz", - "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==", - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz", - "integrity": "sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw==", - "requires": { - "@babel/helper-builder-react-jsx": "^7.9.0", - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/preset-env": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.6.tgz", - "integrity": "sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ==", - "requires": { - "@babel/compat-data": "^7.9.6", - "@babel/helper-compilation-targets": "^7.9.6", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.6", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.5", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.9.5", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.6", - "@babel/plugin-transform-modules-commonjs": "^7.9.6", - "@babel/plugin-transform-modules-systemjs": "^7.9.6", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.9.5", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.6", - "browserslist": "^4.11.1", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/runtime": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", - "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/traverse": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", - "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", - "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", - "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, - "@jest/console": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", - "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", - "requires": { - "@jest/source-map": "^24.9.0", - "chalk": "^2.0.1", - "slash": "^2.0.0" - } - }, - "@jest/fake-timers": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", - "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", - "requires": { - "@jest/types": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-mock": "^24.9.0" - } - }, - "@jest/source-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", - "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.1.15", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "@jest/test-result": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", - "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", - "requires": { - "@jest/console": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/istanbul-lib-coverage": "^2.0.0" - } - }, - "@jest/transform": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", - "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^24.9.0", - "babel-plugin-istanbul": "^5.1.0", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.15", - "jest-haste-map": "^24.9.0", - "jest-regex-util": "^24.9.0", - "jest-util": "^24.9.0", - "micromatch": "^3.1.10", - "pirates": "^4.0.1", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "2.4.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/babel__core": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", - "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", - "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz", - "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==", - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" - }, - "@types/yargs": { - "version": "13.0.8", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz", - "integrity": "sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" - }, - "ansi-styles": { - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "babel-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", - "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", - "requires": { - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/babel__core": "^7.1.0", - "babel-plugin-istanbul": "^5.1.0", - "babel-preset-jest": "^24.9.0", - "chalk": "^2.4.2", - "slash": "^2.0.0" - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-istanbul": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", - "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "find-up": "^3.0.0", - "istanbul-lib-instrument": "^3.3.0", - "test-exclude": "^5.2.3" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - } - } - }, - "babel-plugin-jest-hoist": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", - "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", - "requires": { - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", - "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", - "requires": { - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^24.9.0" - } - }, - "babylon": { - "version": "7.0.0-beta.47", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", - "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "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==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "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==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "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.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", - "requires": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "requires": { - "node-int64": "^0.4.0" - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "caniuse-lite": { - "version": "1.0.30001054", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001054.tgz", - "integrity": "sha512-jiKlTI6Ur8Kjfj8z0muGrV6FscpRvefcQVPSuMuXnvRCfExU7zlVLNjmOz1TnurWgUrAY7MMmjyy+uTgIl1XHw==" - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "requires": { - "rsvp": "^4.8.4" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "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=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" - }, - "core-js-compat": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", - "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", - "requires": { - "browserslist": "^4.8.5", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "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==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "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==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "electron-to-chromium": { - "version": "1.3.432", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.432.tgz", - "integrity": "sha512-/GdNhXyLP5Yl2322CUX/+Xi8NhdHBqL6lD9VJVKjH6CjoPGakvwZ5CpKgj/oOlbzuWWjOvMjDw1bBuAIRCNTlw==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==" - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "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.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "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==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "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==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "fast-async": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/fast-async/-/fast-async-7.0.6.tgz", - "integrity": "sha512-/iUa3eSQC+Xh5tN6QcVLsEsN7b1DaPIoTZo++VpLLIxtdNW2tEmMZex4TcrMeRnBwMOpZwue2CB171wjt5Kgqg==", - "requires": { - "@babel/generator": "^7.0.0-beta.44", - "@babel/helper-module-imports": "^7.0.0-beta.44", - "babylon": "^7.0.0-beta.44", - "nodent-runtime": "^3.2.1", - "nodent-transform": "^3.2.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "requires": { - "bser": "2.1.1" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "map-cache": "^0.2.2" - } - }, - "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.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "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=", - "requires": { - "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=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "requires": { - "ci-info": "^2.0.0" - } - }, - "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=", - "requires": { - "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=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "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==" - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "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=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==" - }, - "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "jest-haste-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", - "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", - "requires": { - "@jest/types": "^24.9.0", - "anymatch": "^2.0.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.7", - "graceful-fs": "^4.1.15", - "invariant": "^2.2.4", - "jest-serializer": "^24.9.0", - "jest-util": "^24.9.0", - "jest-worker": "^24.9.0", - "micromatch": "^3.1.10", - "sane": "^4.0.3", - "walker": "^1.0.7" - } - }, - "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", - "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", - "requires": { - "@jest/types": "^24.9.0" - } - }, - "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==" - }, - "jest-serializer": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", - "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==" - }, - "jest-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", - "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", - "requires": { - "@jest/console": "^24.9.0", - "@jest/fake-timers": "^24.9.0", - "@jest/source-map": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "callsites": "^3.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.15", - "is-ci": "^2.0.0", - "mkdirp": "^0.5.1", - "slash": "^2.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "requires": { - "leven": "^3.1.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "requires": { - "tmpl": "1.0.x" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "requires": { - "object-visit": "^1.0.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "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.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "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.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" - }, - "node-releases": { - "version": "1.1.55", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.55.tgz", - "integrity": "sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w==" - }, - "nodent-runtime": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/nodent-runtime/-/nodent-runtime-3.2.1.tgz", - "integrity": "sha512-7Ws63oC+215smeKJQCxzrK21VFVlCFBkwl0MOObt0HOpVQXs3u483sAmtkF33nNqZ5rSOQjB76fgyPBmAUrtCA==" - }, - "nodent-transform": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/nodent-transform/-/nodent-transform-3.2.9.tgz", - "integrity": "sha512-4a5FH4WLi+daH/CGD5o/JWRR8W5tlCkd3nrDSkxbOzscJTyTUITltvOJeQjg3HJ1YgEuNyiPhQbvbtRjkQBByQ==" - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "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=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "requires": { - "find-up": "^2.1.0" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - } - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "requires": { - "util.promisify": "^1.0.0" - } - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" - }, - "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", - "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "~0.1.10" - } - }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "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==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "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==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "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=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.0" - } - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "requires": { - "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=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - } - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "requires": { - "makeerror": "1.0.x" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", - "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "@types/estree": { - "version": "0.0.44", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", - "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==" - }, - "@types/node": { - "version": "13.13.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", - "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" - }, - "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" - }, - "acorn": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", - "requires": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "requires": { - "callsites": "^2.0.0" - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001054", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001054.tgz", - "integrity": "sha512-jiKlTI6Ur8Kjfj8z0muGrV6FscpRvefcQVPSuMuXnvRCfExU7zlVLNjmOz1TnurWgUrAY7MMmjyy+uTgIl1XHw==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" - }, - "css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "requires": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - } - }, - "css-modules-loader-core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", - "integrity": "sha1-WQhmgpShvs0mGuCkziGwtVHyHRY=", - "requires": { - "icss-replace-symbols": "1.1.0", - "postcss": "6.0.1", - "postcss-modules-extract-imports": "1.1.0", - "postcss-modules-local-by-default": "1.2.0", - "postcss-modules-scope": "1.1.0", - "postcss-modules-values": "1.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" - }, - "postcss": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz", - "integrity": "sha1-AA29H47vIXqjaLmiEsX8QLKo8/I=", - "requires": { - "chalk": "^1.1.3", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "css-selector-tokenizer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", - "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", - "requires": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2", - "regexpu-core": "^4.6.0" - } - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-what": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", - "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==" - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" - }, - "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "requires": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - } - }, - "cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" - }, - "cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" - }, - "cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "requires": { - "postcss": "^7.0.0" - } - }, - "cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" - }, - "csso": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", - "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", - "requires": { - "css-tree": "1.0.0-alpha.39" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0-alpha.39", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", - "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", - "requires": { - "mdn-data": "2.0.6", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", - "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" - } - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "electron-to-chromium": { - "version": "1.3.432", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.432.tgz", - "integrity": "sha512-/GdNhXyLP5Yl2322CUX/+Xi8NhdHBqL6lD9VJVKjH6CjoPGakvwZ5CpKgj/oOlbzuWWjOvMjDw1bBuAIRCNTlw==" - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" - }, - "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==" - }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "generic-names": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz", - "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==", - "requires": { - "loader-utils": "^1.1.0" - } - }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" - }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" - }, - "import-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", - "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", - "requires": { - "import-from": "^3.0.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "import-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", - "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - } - } - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-reference": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", - "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", - "requires": { - "@types/estree": "0.0.39" - }, - "dependencies": { - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" - } - } - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - } - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "requires": { - "chalk": "^2.4.2" - } - }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-releases": { - "version": "1.1.55", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.55.tgz", - "integrity": "sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w==" - }, - "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-queue": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.4.0.tgz", - "integrity": "sha512-X7ddxxiQ+bLR/CUt3/BVKrGcJDNxBr0pEEFKHHB6vTPWNUhgDv36GpIH18RmGM3YGPpBT+JWGjDDqsVGuF0ERw==", - "requires": { - "eventemitter3": "^4.0.0", - "p-timeout": "^3.1.0" - } - }, - "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "requires": { - "p-finally": "^1.0.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "requires": { - "find-up": "^2.1.0" - } - }, - "postcss": { - "version": "7.0.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.29.tgz", - "integrity": "sha512-ba0ApvR3LxGvRMMiUa9n0WR4HjzcYm7tS+ht4/2Nd0NLtHpPIH77fuB9Xh1/yJVz9O/E/95Y/dn8ygWsyffXtw==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-calc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", - "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", - "requires": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - }, - "dependencies": { - "import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "requires": { - "import-from": "^2.1.0" - } - }, - "import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "requires": { - "resolve-from": "^3.0.0" - } - } - } - }, - "postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "requires": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-2.0.0.tgz", - "integrity": "sha512-eqp+Bva+U2cwQO7dECJ8/V+X+uH1HduNeITB0CPPFAu6d/8LKQ32/j+p9rQ2YL1QytVcrNU0X+fBqgGmQIA1Rw==", - "requires": { - "css-modules-loader-core": "^1.1.0", - "generic-names": "^2.0.1", - "lodash.camelcase": "^4.3.0", - "postcss": "^7.0.1", - "string-hash": "^1.1.1" - } - }, - "postcss-modules-extract-imports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", - "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", - "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "requires": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", - "requires": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", - "requires": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "requires": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "promise.series": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", - "integrity": "sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "requires": { - "regenerate": "^1.4.0" - } - }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - } - } - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" - }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" - }, - "rollup": { - "version": "1.32.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", - "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", - "requires": { - "@types/estree": "*", - "@types/node": "*", - "acorn": "^7.1.0" - } - }, - "rollup-plugin-babel": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", - "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-commonjs": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", - "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", - "requires": { - "estree-walker": "^0.6.1", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-postcss": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-2.9.0.tgz", - "integrity": "sha512-Y7qDwlqjZMBexbB1kRJf+jKIQL8HR6C+ay53YzN+nNJ64hn1PNZfBE3c61hFUhD//zrMwmm7uBW30RuTi+CD0w==", - "requires": { - "chalk": "^4.0.0", - "concat-with-sourcemaps": "^1.1.0", - "cssnano": "^4.1.10", - "import-cwd": "^3.0.0", - "p-queue": "^6.3.0", - "pify": "^5.0.0", - "postcss": "^7.0.27", - "postcss-load-config": "^2.1.0", - "postcss-modules": "^2.0.0", - "promise.series": "^0.2.0", - "resolve": "^1.16.0", - "rollup-pluginutils": "^2.8.2", - "safe-identifier": "^0.4.1", - "style-inject": "^0.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "requires": { - "estree-walker": "^0.6.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-identifier": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.1.tgz", - "integrity": "sha512-73tOz5TXsq3apuCc3vC8c9QRhhdNZGiBhHmPPjqpH4TO5oCDqk8UIsDcSs/RG6dYcFAkOOva0pqHS3u7hh7XXA==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "string-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", - "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=" - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "style-inject": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", - "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==" - }, - "stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - } - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" - } - } - }, - "@financial-times/x-test-utils": { - "requires": { - "enzyme": "^3.6.0", - "enzyme-adapter-react-16": "^1.5.0", - "jest-enzyme": "^6.0.4", - "react": "^16.5.0", - "react-dom": "^16.5.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@types/node": { - "version": "13.13.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", - "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" - }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" - }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" - } - } - }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" - }, - "airbnb-prop-types": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz", - "integrity": "sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA==", - "requires": { - "array.prototype.find": "^2.1.0", - "function.prototype.name": "^1.1.1", - "has": "^1.0.3", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object.assign": "^4.1.0", - "object.entries": "^1.1.0", - "prop-types": "^15.7.2", - "prop-types-exact": "^1.2.0", - "react-is": "^16.9.0" - } - }, - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-styles": { - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" - }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" - }, - "array.prototype.find": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", - "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.4" - } - }, - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cheerio": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", - "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.1", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" - }, - "circular-json-es6": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", - "integrity": "sha512-ODYONMMNb3p658Zv+Pp+/XPa5s6q7afhz3Tzyvo+VRh9WIrJ64J76ZC4GQxnlye/NesTn09jvOiuE8+xxfpwhQ==" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", - "requires": { - "cssom": "0.3.x" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "deep-equal-ident": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz", - "integrity": "sha1-BvS4nlNxDNbOpKd4HHqVZkLejck=", - "requires": { - "lodash.isequal": "^3.0" - }, - "dependencies": { - "lodash.isequal": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-3.0.4.tgz", - "integrity": "sha1-HDXrO27wzR/1F0Pj6jz3/f/ay2Q=", - "requires": { - "lodash._baseisequal": "^3.0.0", - "lodash._bindcallback": "^3.0.0" - } - } - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "enzyme": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", - "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", - "requires": { - "array.prototype.flat": "^1.2.3", - "cheerio": "^1.0.0-rc.3", - "enzyme-shallow-equal": "^1.0.1", - "function.prototype.name": "^1.1.2", - "has": "^1.0.3", - "html-element-map": "^1.2.0", - "is-boolean-object": "^1.0.1", - "is-callable": "^1.1.5", - "is-number-object": "^1.0.4", - "is-regex": "^1.0.5", - "is-string": "^1.0.5", - "is-subset": "^0.1.1", - "lodash.escape": "^4.0.1", - "lodash.isequal": "^4.5.0", - "object-inspect": "^1.7.0", - "object-is": "^1.0.2", - "object.assign": "^4.1.0", - "object.entries": "^1.1.1", - "object.values": "^1.1.1", - "raf": "^3.4.1", - "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.2.1" - } - }, - "enzyme-adapter-react-16": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz", - "integrity": "sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==", - "requires": { - "enzyme-adapter-utils": "^1.13.0", - "enzyme-shallow-equal": "^1.0.1", - "has": "^1.0.3", - "object.assign": "^4.1.0", - "object.values": "^1.1.1", - "prop-types": "^15.7.2", - "react-is": "^16.12.0", - "react-test-renderer": "^16.0.0-0", - "semver": "^5.7.0" - } - }, - "enzyme-adapter-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz", - "integrity": "sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ==", - "requires": { - "airbnb-prop-types": "^2.15.0", - "function.prototype.name": "^1.1.2", - "object.assign": "^4.1.0", - "object.fromentries": "^2.0.2", - "prop-types": "^15.7.2", - "semver": "^5.7.1" - } - }, - "enzyme-matchers": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-6.1.2.tgz", - "integrity": "sha512-cP9p+HMOZ1ZXQ+k2H4dCkxmTZzIvpEy5zv0ZjgoBl6D0U43v+bJGH5IeWHdIovCzgJ0dVcMCKJ6lNu83lYUCAA==", - "requires": { - "circular-json-es6": "^2.0.1", - "deep-equal-ident": "^1.1.1" - } - }, - "enzyme-shallow-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz", - "integrity": "sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==", - "requires": { - "has": "^1.0.3", - "object-is": "^1.0.2" - } - }, - "enzyme-to-json": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.4.4.tgz", - "integrity": "sha512-50LELP/SCPJJGic5rAARvU7pgE3m1YaNj7JLM+Qkhl5t7PAs6fiyc8xzc50RnkKPFQCv0EeFVjEWdIFRGPWMsA==", - "requires": { - "lodash": "^4.17.15", - "react-is": "^16.12.0" - } - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "requires": { - "fill-range": "^2.1.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" - }, - "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==", - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "requires": { - "for-in": "^1.0.1" - } - }, - "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.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "function.prototype.name": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", - "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "functions-have-names": "^1.2.0" - } - }, - "functions-have-names": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", - "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "requires": { - "is-glob": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "html-element-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", - "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", - "requires": { - "array-filter": "^1.0.0" - } - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" - }, - "is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jest-environment-enzyme": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-6.1.2.tgz", - "integrity": "sha512-WHeBKgBYOdryuOTEoK55lJwjg7Raery1OgXHLwukI3mSYgOkm2UrCDDT+vneqVgy7F8KuRHyStfD+TC/m2b7Kg==", - "requires": { - "jest-environment-jsdom": "^22.4.1" - } - }, - "jest-environment-jsdom": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", - "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", - "requires": { - "jest-mock": "^22.4.3", - "jest-util": "^22.4.3", - "jsdom": "^11.5.1" - } - }, - "jest-enzyme": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.1.2.tgz", - "integrity": "sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q==", - "requires": { - "enzyme-matchers": "^6.1.2", - "enzyme-to-json": "^3.3.0", - "jest-environment-enzyme": "^6.1.2" - } - }, - "jest-message-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", - "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", - "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==" - }, - "jest-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", - "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^22.4.3", - "mkdirp": "^0.5.1", - "source-map": "^0.6.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" - } - } - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "lodash._baseisequal": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", - "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", - "requires": { - "lodash.isarray": "^3.0.0", - "lodash.istypedarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "lodash.istypedarray": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", - "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=" - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "moo": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", - "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==" - }, - "nearley": { - "version": "2.19.3", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.3.tgz", - "integrity": "sha512-FpAy1PmTsUpOtgxr23g4jRNvJHYzZEW2PixXeSzksLR/ykPfwKhAodc2+9wQhY+JneWLcvkDw6q7FJIsIdF/aQ==", - "requires": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6", - "semver": "^5.4.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" - }, - "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.entries": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", - "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "requires": { - "@types/node": "*" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "prop-types-exact": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", - "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", - "requires": { - "has": "^1.0.3", - "object.assign": "^4.1.0", - "reflect.ownkeys": "^0.2.0" - } - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "requires": { - "performance-now": "^2.1.0" - } - }, - "railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" - }, - "randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "requires": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - } - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - } - } - }, - "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-dom": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", - "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-test-renderer": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.13.1.tgz", - "integrity": "sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==", - "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.19.1" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "reflect.ownkeys": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", - "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=" - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "requires": { - "lodash": "^4.17.15" - } - }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "rst-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", - "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", - "requires": { - "lodash.flattendeep": "^4.4.0", - "nearley": "^2.7.10" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, - "string.prototype.trim": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", - "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "requires": { - "punycode": "^2.1.0" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - } - } - }, - "@quarterto/short-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@quarterto/short-id/-/short-id-1.1.0.tgz", - "integrity": "sha1-m5EekORhSHqEDMBihpQUv7uvua0=" - } - } - }, - "@financial-times/x-rollup": { - "version": "file:../../packages/x-rollup", - "dev": true, - "requires": { - "@babel/core": "^7.6.4", - "@babel/plugin-external-helpers": "^7.2.0", - "@financial-times/x-babel-config": "file:../../packages/x-babel-config", - "chalk": "^2.4.2", - "log-symbols": "^3.0.0", - "rollup": "^1.23.0", - "rollup-plugin-babel": "^4.3.2", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-postcss": "^2.0.2" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/core": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", - "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.6", - "@babel/parser": "^7.9.6", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/generator": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", - "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", - "requires": { - "@babel/types": "^7.9.6", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" - }, - "@babel/helper-replace-supers": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", - "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6" - } - }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" - }, - "@babel/helpers": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", - "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", - "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6" - } - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==" - }, - "@babel/plugin-external-helpers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.8.3.tgz", - "integrity": "sha512-mx0WXDDiIl5DwzMtzWGRSPugXi9BxROS05GQrhLNbEamhBiicgn994ibwkyiBH+6png7bm/yA7AUsvHyCXi4Vw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/traverse": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", - "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", - "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", - "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "@financial-times/x-babel-config": { - "version": "file:../../packages/x-babel-config", - "requires": { - "@babel/plugin-transform-react-jsx": "^7.3.0", - "@babel/preset-env": "^7.4.3", - "babel-jest": "^24.0.0", - "fast-async": "^7.0.6" - }, - "dependencies": {} - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "@types/estree": { - "version": "0.0.44", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", - "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==" - }, - "@types/node": { - "version": "13.13.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", - "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" - }, - "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" - }, - "acorn": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", - "requires": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "requires": { - "callsites": "^2.0.0" - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001054", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001054.tgz", - "integrity": "sha512-jiKlTI6Ur8Kjfj8z0muGrV6FscpRvefcQVPSuMuXnvRCfExU7zlVLNjmOz1TnurWgUrAY7MMmjyy+uTgIl1XHw==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": {} - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" - }, - "css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "requires": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - } - }, - "css-modules-loader-core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", - "integrity": "sha1-WQhmgpShvs0mGuCkziGwtVHyHRY=", - "requires": { - "icss-replace-symbols": "1.1.0", - "postcss": "6.0.1", - "postcss-modules-extract-imports": "1.1.0", - "postcss-modules-local-by-default": "1.2.0", - "postcss-modules-scope": "1.1.0", - "postcss-modules-values": "1.3.0" - }, - "dependencies": {} - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "css-selector-tokenizer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", - "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", - "requires": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2", - "regexpu-core": "^4.6.0" - } - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "dependencies": {} - }, - "css-what": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", - "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==" - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" - }, - "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "requires": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - } - }, - "cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" - }, - "cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" - }, - "cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "requires": { - "postcss": "^7.0.0" - } - }, - "cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" - }, - "csso": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", - "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", - "requires": { - "css-tree": "1.0.0-alpha.39" - }, - "dependencies": {} - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - }, - "dependencies": {} - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "electron-to-chromium": { - "version": "1.3.432", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.432.tgz", - "integrity": "sha512-/GdNhXyLP5Yl2322CUX/+Xi8NhdHBqL6lD9VJVKjH6CjoPGakvwZ5CpKgj/oOlbzuWWjOvMjDw1bBuAIRCNTlw==" - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" - }, - "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==" - }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "generic-names": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz", - "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==", - "requires": { - "loader-utils": "^1.1.0" - } - }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" - }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" - }, - "import-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", - "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", - "requires": { - "import-from": "^3.0.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "import-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", - "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": {} - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-reference": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", - "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", - "requires": { - "@types/estree": "0.0.39" - }, - "dependencies": {} - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "dependencies": {} - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "requires": { - "chalk": "^2.4.2" - } - }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-releases": { - "version": "1.1.55", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.55.tgz", - "integrity": "sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w==" - }, - "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-queue": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.4.0.tgz", - "integrity": "sha512-X7ddxxiQ+bLR/CUt3/BVKrGcJDNxBr0pEEFKHHB6vTPWNUhgDv36GpIH18RmGM3YGPpBT+JWGjDDqsVGuF0ERw==", - "requires": { - "eventemitter3": "^4.0.0", - "p-timeout": "^3.1.0" - } - }, - "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "requires": { - "p-finally": "^1.0.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "requires": { - "find-up": "^2.1.0" - } - }, - "postcss": { - "version": "7.0.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.29.tgz", - "integrity": "sha512-ba0ApvR3LxGvRMMiUa9n0WR4HjzcYm7tS+ht4/2Nd0NLtHpPIH77fuB9Xh1/yJVz9O/E/95Y/dn8ygWsyffXtw==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": {} - }, - "postcss-calc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", - "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", - "requires": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - }, - "dependencies": {} - }, - "postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "requires": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "dependencies": {} - }, - "postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "dependencies": {} - }, - "postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "dependencies": {} - }, - "postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-2.0.0.tgz", - "integrity": "sha512-eqp+Bva+U2cwQO7dECJ8/V+X+uH1HduNeITB0CPPFAu6d/8LKQ32/j+p9rQ2YL1QytVcrNU0X+fBqgGmQIA1Rw==", - "requires": { - "css-modules-loader-core": "^1.1.0", - "generic-names": "^2.0.1", - "lodash.camelcase": "^4.3.0", - "postcss": "^7.0.1", - "string-hash": "^1.1.1" - } - }, - "postcss-modules-extract-imports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", - "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": {} - }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": {} - }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": {} - }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", - "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" - }, - "dependencies": {} - }, - "postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "requires": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": {} - }, - "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", - "requires": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", - "requires": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - }, - "dependencies": {} - }, - "postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "requires": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "promise.series": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", - "integrity": "sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "requires": { - "regenerate": "^1.4.0" - } - }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": {} - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" - }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" - }, - "rollup": { - "version": "1.32.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", - "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", - "requires": { - "@types/estree": "*", - "@types/node": "*", - "acorn": "^7.1.0" - } - }, - "rollup-plugin-babel": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", - "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-commonjs": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", - "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", - "requires": { - "estree-walker": "^0.6.1", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-postcss": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-2.9.0.tgz", - "integrity": "sha512-Y7qDwlqjZMBexbB1kRJf+jKIQL8HR6C+ay53YzN+nNJ64hn1PNZfBE3c61hFUhD//zrMwmm7uBW30RuTi+CD0w==", - "requires": { - "chalk": "^4.0.0", - "concat-with-sourcemaps": "^1.1.0", - "cssnano": "^4.1.10", - "import-cwd": "^3.0.0", - "p-queue": "^6.3.0", - "pify": "^5.0.0", - "postcss": "^7.0.27", - "postcss-load-config": "^2.1.0", - "postcss-modules": "^2.0.0", - "promise.series": "^0.2.0", - "resolve": "^1.16.0", - "rollup-pluginutils": "^2.8.2", - "safe-identifier": "^0.4.1", - "style-inject": "^0.3.0" - }, - "dependencies": {} - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "requires": { - "estree-walker": "^0.6.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-identifier": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.1.tgz", - "integrity": "sha512-73tOz5TXsq3apuCc3vC8c9QRhhdNZGiBhHmPPjqpH4TO5oCDqk8UIsDcSs/RG6dYcFAkOOva0pqHS3u7hh7XXA==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": {} - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "string-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", - "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=" - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "style-inject": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", - "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==" - }, - "stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": {} - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - } - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" - } - } - }, - "@financial-times/x-test-utils": { - "version": "file:../../packages/x-test-utils", - "dev": true, - "requires": { - "enzyme": "^3.6.0", - "enzyme-adapter-react-16": "^1.5.0", - "jest-enzyme": "^6.0.4", - "react": "^16.5.0", - "react-dom": "^16.5.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@types/node": { - "version": "13.13.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", - "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" - }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" - }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": {} - }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" - }, - "airbnb-prop-types": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz", - "integrity": "sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA==", - "requires": { - "array.prototype.find": "^2.1.0", - "function.prototype.name": "^1.1.1", - "has": "^1.0.3", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object.assign": "^4.1.0", - "object.entries": "^1.1.0", - "prop-types": "^15.7.2", - "prop-types-exact": "^1.2.0", - "react-is": "^16.9.0" - } - }, - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-styles": { - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" - }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" - }, - "array.prototype.find": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", - "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.4" - } - }, - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cheerio": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", - "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.1", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" - }, - "circular-json-es6": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", - "integrity": "sha512-ODYONMMNb3p658Zv+Pp+/XPa5s6q7afhz3Tzyvo+VRh9WIrJ64J76ZC4GQxnlye/NesTn09jvOiuE8+xxfpwhQ==" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", - "requires": { - "cssom": "0.3.x" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": {} - }, - "deep-equal-ident": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz", - "integrity": "sha1-BvS4nlNxDNbOpKd4HHqVZkLejck=", - "requires": { - "lodash.isequal": "^3.0" - }, - "dependencies": {} - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "enzyme": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", - "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", - "requires": { - "array.prototype.flat": "^1.2.3", - "cheerio": "^1.0.0-rc.3", - "enzyme-shallow-equal": "^1.0.1", - "function.prototype.name": "^1.1.2", - "has": "^1.0.3", - "html-element-map": "^1.2.0", - "is-boolean-object": "^1.0.1", - "is-callable": "^1.1.5", - "is-number-object": "^1.0.4", - "is-regex": "^1.0.5", - "is-string": "^1.0.5", - "is-subset": "^0.1.1", - "lodash.escape": "^4.0.1", - "lodash.isequal": "^4.5.0", - "object-inspect": "^1.7.0", - "object-is": "^1.0.2", - "object.assign": "^4.1.0", - "object.entries": "^1.1.1", - "object.values": "^1.1.1", - "raf": "^3.4.1", - "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.2.1" - } - }, - "enzyme-adapter-react-16": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz", - "integrity": "sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==", - "requires": { - "enzyme-adapter-utils": "^1.13.0", - "enzyme-shallow-equal": "^1.0.1", - "has": "^1.0.3", - "object.assign": "^4.1.0", - "object.values": "^1.1.1", - "prop-types": "^15.7.2", - "react-is": "^16.12.0", - "react-test-renderer": "^16.0.0-0", - "semver": "^5.7.0" - } - }, - "enzyme-adapter-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz", - "integrity": "sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ==", - "requires": { - "airbnb-prop-types": "^2.15.0", - "function.prototype.name": "^1.1.2", - "object.assign": "^4.1.0", - "object.fromentries": "^2.0.2", - "prop-types": "^15.7.2", - "semver": "^5.7.1" - } - }, - "enzyme-matchers": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-6.1.2.tgz", - "integrity": "sha512-cP9p+HMOZ1ZXQ+k2H4dCkxmTZzIvpEy5zv0ZjgoBl6D0U43v+bJGH5IeWHdIovCzgJ0dVcMCKJ6lNu83lYUCAA==", - "requires": { - "circular-json-es6": "^2.0.1", - "deep-equal-ident": "^1.1.1" - } - }, - "enzyme-shallow-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz", - "integrity": "sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==", - "requires": { - "has": "^1.0.3", - "object-is": "^1.0.2" - } - }, - "enzyme-to-json": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.4.4.tgz", - "integrity": "sha512-50LELP/SCPJJGic5rAARvU7pgE3m1YaNj7JLM+Qkhl5t7PAs6fiyc8xzc50RnkKPFQCv0EeFVjEWdIFRGPWMsA==", - "requires": { - "lodash": "^4.17.15", - "react-is": "^16.12.0" - } - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "requires": { - "fill-range": "^2.1.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" - }, - "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==", - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "requires": { - "for-in": "^1.0.1" - } - }, - "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.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "function.prototype.name": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", - "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "functions-have-names": "^1.2.0" - } - }, - "functions-have-names": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", - "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "requires": { - "is-glob": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "html-element-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", - "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", - "requires": { - "array-filter": "^1.0.0" - } - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" - }, - "is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jest-environment-enzyme": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-6.1.2.tgz", - "integrity": "sha512-WHeBKgBYOdryuOTEoK55lJwjg7Raery1OgXHLwukI3mSYgOkm2UrCDDT+vneqVgy7F8KuRHyStfD+TC/m2b7Kg==", - "requires": { - "jest-environment-jsdom": "^22.4.1" - } - }, - "jest-environment-jsdom": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", - "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", - "requires": { - "jest-mock": "^22.4.3", - "jest-util": "^22.4.3", - "jsdom": "^11.5.1" - } - }, - "jest-enzyme": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.1.2.tgz", - "integrity": "sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q==", - "requires": { - "enzyme-matchers": "^6.1.2", - "enzyme-to-json": "^3.3.0", - "jest-environment-enzyme": "^6.1.2" - } - }, - "jest-message-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", - "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", - "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==" - }, - "jest-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", - "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^22.4.3", - "mkdirp": "^0.5.1", - "source-map": "^0.6.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - }, - "dependencies": {} - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "lodash._baseisequal": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", - "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", - "requires": { - "lodash.isarray": "^3.0.0", - "lodash.istypedarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "lodash.istypedarray": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", - "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=" - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "moo": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", - "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==" - }, - "nearley": { - "version": "2.19.3", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.3.tgz", - "integrity": "sha512-FpAy1PmTsUpOtgxr23g4jRNvJHYzZEW2PixXeSzksLR/ykPfwKhAodc2+9wQhY+JneWLcvkDw6q7FJIsIdF/aQ==", - "requires": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6", - "semver": "^5.4.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" - }, - "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.entries": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", - "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "requires": { - "@types/node": "*" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "prop-types-exact": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", - "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", - "requires": { - "has": "^1.0.3", - "object.assign": "^4.1.0", - "reflect.ownkeys": "^0.2.0" - } - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "requires": { - "performance-now": "^2.1.0" - } - }, - "railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" - }, - "randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "requires": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - } - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": {} - }, - "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-dom": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", - "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-test-renderer": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.13.1.tgz", - "integrity": "sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==", - "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.19.1" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "reflect.ownkeys": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", - "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=" - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "requires": { - "lodash": "^4.17.15" - } - }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "rst-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", - "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", - "requires": { - "lodash.flattendeep": "^4.4.0", - "nearley": "^2.7.10" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, - "string.prototype.trim": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", - "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "requires": { - "punycode": "^2.1.0" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - } - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - } - } - }, - "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true - }, - "bower": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.8.tgz", - "integrity": "sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" - } - }, - "classnames": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" - }, - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fetch-mock": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.9.0.tgz", - "integrity": "sha512-2/0FJo23c6xTYQqBKhp2t1kEeJqWcoPCTMDShJx4CS3w0Vjdi2/VO6nKVrcMJyurQEIvZBHZpdIWYdHvLTiA4A==", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^3.0.0", - "debug": "^4.1.1", - "glob-to-regexp": "^0.4.0", - "is-subset": "^0.1.1", - "lodash.isequal": "^4.5.0", - "path-to-regexp": "^2.2.1", - "querystring": "^0.2.0", - "whatwg-url": "^6.5.0" - } - }, - "fetch-mock-jest": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fetch-mock-jest/-/fetch-mock-jest-1.3.0.tgz", - "integrity": "sha512-3hHLcSQrww8yhGQnUyKSCQik4piaWcjHc4/bdDfKCQI6GEOsOKBWJro/XBXGnZjZPy47cVtBDx99aQxNYK0/OA==", - "dev": true, - "requires": { - "fetch-mock": "^9.0.0" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", - "dev": true - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "path-to-regexp": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz", - "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==", - "dev": true - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "sass": { - "version": "1.26.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.5.tgz", - "integrity": "sha512-FG2swzaZUiX53YzZSjSakzvGtlds0lcbF+URuU9mxOv7WBh7NhXEVDa4kPKN4hN6fC2TkOTOKqiqp6d53N9X5Q==", - "dev": true, - "requires": { - "chokidar": ">=2.0.0 <4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } -} From 9257b9aba88ea64268980d3064c390631b28a3c4 Mon Sep 17 00:00:00 2001 From: Oliver Turner Date: Tue, 19 May 2020 17:24:12 +0100 Subject: [PATCH 398/760] Improve tests --- .../privacy-manager.test.jsx.snap | 863 ++++++++++++++++++ .../src/__tests__/privacy-manager.test.jsx | 87 +- .../x-privacy-manager/src/privacy-manager.jsx | 28 +- components/x-privacy-manager/stories/index.js | 8 +- ...-accepted.js => story-consent-accepted.js} | 4 +- ...nt-blocked.js => story-consent-blocked.js} | 4 +- ...nate.js => story-consent-indeterminate.js} | 4 +- .../save-failed.js => story-save-failed.js} | 5 +- 8 files changed, 964 insertions(+), 39 deletions(-) create mode 100644 components/x-privacy-manager/src/__tests__/__snapshots__/privacy-manager.test.jsx.snap rename components/x-privacy-manager/stories/{story/consent-accepted.js => story-consent-accepted.js} (79%) rename components/x-privacy-manager/stories/{story/consent-blocked.js => story-consent-blocked.js} (79%) rename components/x-privacy-manager/stories/{story/consent-indeterminate.js => story-consent-indeterminate.js} (80%) rename components/x-privacy-manager/stories/{story/save-failed.js => story-save-failed.js} (81%) diff --git a/components/x-privacy-manager/src/__tests__/__snapshots__/privacy-manager.test.jsx.snap b/components/x-privacy-manager/src/__tests__/__snapshots__/privacy-manager.test.jsx.snap new file mode 100644 index 000000000..ad3107ec4 --- /dev/null +++ b/components/x-privacy-manager/src/__tests__/__snapshots__/privacy-manager.test.jsx.snap @@ -0,0 +1,863 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`x-privacy-manager It displays the appropriate messaging None by default 1`] = ` + +
      +

      + Do Not Sell My Personal Information +

      +
      +

      + CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. +

      +

      + Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. +

      +
      +
      +

      + Use of my personal information for advertising purposes +

      +
      + +
      + + +
      +
      + +
      + + +
      +
      +
      + +
      +
      +
      +
      +`; + +exports[`x-privacy-manager It displays the appropriate messaging On receiving a response with a non-200 status 1`] = ` + +
      +

      + Do Not Sell My Personal Information +

      +
      +

      + CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. +

      +

      + Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. +

      +
      + + +
      +
      +
      +
      + Your setting could not be saved. Please try again later. +   + + Continue to homepage + +
      +
      +
      +
      +
      +
      +
      +

      + Use of my personal information for advertising purposes +

      +
      + +
      + + +
      +
      + +
      + + +
      +
      +
      + +
      +
      +
      +
      +`; + +exports[`x-privacy-manager It displays the appropriate messaging On receiving a response with a status of 200 1`] = ` + +
      +

      + Do Not Sell My Personal Information +

      +
      +

      + CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. +

      +

      + Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. +

      +
      + + +
      +
      +
      +
      + Your setting has been saved. +   + + Continue to homepage + +
      +
      +
      +
      +
      +
      +
      +

      + Use of my personal information for advertising purposes +

      +
      + +
      + + +
      +
      + +
      + + +
      +
      +
      + +
      +
      +
      +
      +`; + +exports[`x-privacy-manager It displays the appropriate messaging While loading 1`] = ` + +
      +

      + Do Not Sell My Personal Information +

      +
      +

      + CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. +

      +

      + Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. +

      +
      + + +
      +
      +
      +
      +
      + + Loading... + +
      +
      +
      +
      + + +
      +

      + Use of my personal information for advertising purposes +

      +
      + +
      + + +
      +
      + +
      + + +
      +
      +
      + +
      +
      +
      + +`; + +exports[`x-privacy-manager initial state handles a change of consent 1`] = ` + + + + +
      +

      + Do Not Sell My Personal Information +

      +
      +

      + CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. +

      +

      + Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. +

      +
      + + +
      +
      +
      +
      + Your setting has been saved. +   + + Continue to homepage + +
      +
      +
      +
      +
      +
      +
      +

      + Use of my personal information for advertising purposes +

      +
      + +
      + + +
      +
      + +
      + + +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +`; diff --git a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx index adea6406c..c2379fc1d 100644 --- a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx +++ b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx @@ -1,27 +1,73 @@ const { h } = require('@financial-times/x-engine'); const { mount } = require('@financial-times/x-test-utils/enzyme'); -const { Response } = require('node-fetch'); +const fetchMock = require('fetch-mock'); -import { BasePrivacyManager, PrivacyManager } from '../privacy-manager'; +import { CONSENT_API, BasePrivacyManager, PrivacyManager } from '../privacy-manager'; + +fetchMock.mock(CONSENT_API, 200, { delay: 500 }); + +function checkPayload(opts, expected) { + const values = Object.values(JSON.parse(String(opts.body))); + return values.every((v) => v === expected); +} describe('x-privacy-manager', () => { describe('initial state', () => { it('defaults to "Allow"', () => { const subject = mount(); - const input = subject.find('input[value="true"]'); - expect(input.first().prop('checked')).toBe(true); + const inputTrue = subject.find('input[value="true"]').first(); + + // Verify that initial props are correctly reflected + expect(inputTrue.prop('checked')).toBe(true); }); it('highlights explicitly set consent correctly: false', () => { const subject = mount(); - const input = subject.find('input[value="false"]').first(); - expect(input.prop('checked')).toBe(true); + const inputFalse = subject.find('input[value="false"]').first(); + + // Verify that initial props are correctly reflected + expect(inputFalse.prop('checked')).toBe(true); }); it('highlights explicitly set consent correctly: true', () => { const subject = mount(); - const input = subject.find('input[value="true"]').first(); - expect(input.prop('checked')).toBe(true); + const inputTrue = subject.find('input[value="true"]').first(); + + // Verify that initial props are correctly reflected + expect(inputTrue.prop('checked')).toBe(true); + }); + + it('handles a change of consent', async () => { + const subject = mount(); + const form = subject.find('form').first(); + const inputTrue = subject.find('input[value="true"]').first(); + const inputFalse = subject.find('input[value="false"]').first(); + const submitBtn = subject.find('button[type="submit"]'); + + // Switch consent to false and submit form + await inputFalse.prop('onChange')(undefined); + await form.prop('onSubmit')(undefined); + // Check that fetch was called with the correct values + expect(checkPayload(fetchMock.lastOptions(), false)).toBe(true); + + // Switch consent back to true and resubmit form + await inputTrue.prop('onChange')(undefined); + await form.prop('onSubmit')(undefined); + // Check that fetch was called with the correct values + expect(checkPayload(fetchMock.lastOptions(), true)).toBe(true); + + // Reconcile snapshot with state + subject.update(); + + // Verify that confimatory nmessage is displayed + const message = subject.find('[data-o-component="o-message"]').first(); + const link = message.find('[data-component="referrer-link"]'); + expect(message).toHaveClassName('o-message--success'); + expect(link).toHaveProp('href', 'https://www.ft.com/'); + expect(inputTrue).toHaveProp('checked', true); + expect(submitBtn).toHaveProp('disabled', false); + + expect(subject).toMatchSnapshot(); }); }); @@ -31,8 +77,8 @@ describe('x-privacy-manager', () => { referrer: 'www.ft.com', legislation: ['ccpa'], actions: { - onConsentChange: jest.fn(), - onSubmit: jest.fn().mockReturnValue({ _response: new Response() }) + onConsentChange: jest.fn(() => {}), + sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }) }, isLoading: false, _response: undefined @@ -43,6 +89,8 @@ describe('x-privacy-manager', () => { const messages = subject.find('[data-o-component="o-message"]'); expect(messages).toHaveLength(0); + + expect(subject).toMatchSnapshot(); }); it('While loading', () => { @@ -52,10 +100,15 @@ describe('x-privacy-manager', () => { const messages = subject.find('[data-o-component="o-message"]'); expect(messages).toHaveLength(1); expect(messages.first()).toHaveClassName('o-message--neutral'); + + const submitBtn = subject.find('button[type="submit"]'); + expect(submitBtn).toHaveProp('disabled', true); + + expect(subject).toMatchSnapshot(); }); it('On receiving a response with a status of 200', () => { - const _response = new Response('', { status: 200 }); + const _response = { ok: true, status: 200 }; const props = { ...defaultProps, _response }; const subject = mount(); @@ -67,10 +120,15 @@ describe('x-privacy-manager', () => { const link = message.find('[data-component="referrer-link"]'); expect(link).toHaveProp('href', 'https://www.ft.com/'); + + const submitBtn = subject.find('button[type="submit"]'); + expect(submitBtn).toHaveProp('disabled', false); + + expect(subject).toMatchSnapshot(); }); it('On receiving a response with a non-200 status', () => { - const _response = new Response('', { status: 400 }); + const _response = { ok: false, status: 400 }; const props = { ...defaultProps, _response }; const subject = mount(); @@ -82,6 +140,11 @@ describe('x-privacy-manager', () => { const link = message.find('[data-component="referrer-link"]'); expect(link).toHaveProp('href', 'https://www.ft.com/'); + + const submitBtn = subject.find('button[type="submit"]'); + expect(submitBtn).toHaveProp('disabled', false); + + expect(subject).toMatchSnapshot(); }); }); }); diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx index fb467e230..0229572ad 100644 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -1,7 +1,3 @@ -/** - * @typedef { import('react').FormEvent } SubmitEvent - */ - import { h } from '@financial-times/x-engine'; import { withActions } from '@financial-times/x-interaction'; @@ -17,12 +13,7 @@ export const withCustomActions = withActions(() => ({ return ({ consent = true }) => ({ consent: !consent }); }, - /** - * @param {SubmitEvent} event - */ - onSubmit(event) { - event.preventDefault(); - + sendConsent() { return async ({ consent }) => { const body = JSON.stringify({ demographic: consent, @@ -31,8 +22,8 @@ export const withCustomActions = withActions(() => ({ }); try { - const _response = await fetch(CONSENT_API, { method: 'PATCH', body }); - return { _response }; + const res = await fetch(CONSENT_API, { method: 'PATCH', body }); + return { _response: { ok: res.ok } }; } catch (err) { return { _response: { ok: false } }; } @@ -52,16 +43,18 @@ function renderMessage(isLoading, response, referrer) { } /** + * @typedef {{ ok: boolean, status?: number }} _Response + * * @param {{ * consent?: boolean * referrer?: string * legislation?: string[] * actions: { * onConsentChange: () => void - * onSubmit: (event?: SubmitEvent) => Promise<{_response: Response}> + * sendConsent: () => Promise<{_response: _Response }> * }, * isLoading: boolean - * _response: Response | undefined + * _response: _Response | undefined * }} Props */ export function BasePrivacyManager({ @@ -89,7 +82,12 @@ export function BasePrivacyManager({


      {renderMessage(isLoading, _response, referrer)} -
      + { + event && event.preventDefault(); + return actions.sendConsent(consent); + }}>

      Use of my personal information for advertising purposes

      diff --git a/components/x-privacy-manager/stories/index.js b/components/x-privacy-manager/stories/index.js index 5aee23f4e..8cba31024 100644 --- a/components/x-privacy-manager/stories/index.js +++ b/components/x-privacy-manager/stories/index.js @@ -11,10 +11,10 @@ exports.dependencies = { }; exports.stories = [ - require('./story/consent-indeterminate'), - require('./story/consent-accepted'), - require('./story/consent-blocked'), - require('./story/save-failed'), + require('./story-consent-indeterminate'), + require('./story-consent-accepted'), + require('./story-consent-blocked'), + require('./story-save-failed'), ]; exports.knobs = require('./knobs'); diff --git a/components/x-privacy-manager/stories/story/consent-accepted.js b/components/x-privacy-manager/stories/story-consent-accepted.js similarity index 79% rename from components/x-privacy-manager/stories/story/consent-accepted.js rename to components/x-privacy-manager/stories/story-consent-accepted.js index 9601714f3..298a5a64d 100644 --- a/components/x-privacy-manager/stories/story/consent-accepted.js +++ b/components/x-privacy-manager/stories/story-consent-accepted.js @@ -1,5 +1,5 @@ -const data = require('../data'); -const { CONSENT_API } = require('../../src/privacy-manager'); +const data = require('./data'); +const { CONSENT_API } = require('../src/privacy-manager'); exports.title = 'Consent: accepted'; diff --git a/components/x-privacy-manager/stories/story/consent-blocked.js b/components/x-privacy-manager/stories/story-consent-blocked.js similarity index 79% rename from components/x-privacy-manager/stories/story/consent-blocked.js rename to components/x-privacy-manager/stories/story-consent-blocked.js index 5f829b09d..8a9086a5b 100644 --- a/components/x-privacy-manager/stories/story/consent-blocked.js +++ b/components/x-privacy-manager/stories/story-consent-blocked.js @@ -1,5 +1,5 @@ -const data = require('../data'); -const { CONSENT_API } = require('../../src/privacy-manager'); +const data = require('./data'); +const { CONSENT_API } = require('../src/privacy-manager'); exports.title = 'Consent: blocked'; diff --git a/components/x-privacy-manager/stories/story/consent-indeterminate.js b/components/x-privacy-manager/stories/story-consent-indeterminate.js similarity index 80% rename from components/x-privacy-manager/stories/story/consent-indeterminate.js rename to components/x-privacy-manager/stories/story-consent-indeterminate.js index 6573749fd..6c5289cbb 100644 --- a/components/x-privacy-manager/stories/story/consent-indeterminate.js +++ b/components/x-privacy-manager/stories/story-consent-indeterminate.js @@ -1,5 +1,5 @@ -const data = require('../data'); -const { CONSENT_API } = require('../../src/privacy-manager'); +const data = require('./data'); +const { CONSENT_API } = require('../src/privacy-manager'); exports.title = 'Consent: indeterminate'; diff --git a/components/x-privacy-manager/stories/story/save-failed.js b/components/x-privacy-manager/stories/story-save-failed.js similarity index 81% rename from components/x-privacy-manager/stories/story/save-failed.js rename to components/x-privacy-manager/stories/story-save-failed.js index b76fa928e..dfceee525 100644 --- a/components/x-privacy-manager/stories/story/save-failed.js +++ b/components/x-privacy-manager/stories/story-save-failed.js @@ -1,5 +1,6 @@ -const data = require('../data'); -const { CONSENT_API } = require('../../src/privacy-manager'); + +const data = require('./data'); +const { CONSENT_API } = require('../src/privacy-manager'); exports.title = 'Save failed'; From 5d870e3d342580df0ba32e6e15a9f4bb4cb00891 Mon Sep 17 00:00:00 2001 From: Oliver Turner Date: Wed, 20 May 2020 11:14:11 +0100 Subject: [PATCH 399/760] Reflect changes to _response object --- components/x-privacy-manager/src/privacy-manager.jsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx index 0229572ad..19e8b67ea 100644 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -1,3 +1,7 @@ +/** + * @typedef {{ ok: boolean, status?: number }} _Response + */ + import { h } from '@financial-times/x-engine'; import { withActions } from '@financial-times/x-interaction'; @@ -33,7 +37,7 @@ export const withCustomActions = withActions(() => ({ /** * @param {boolean} isLoading - * @param {Response} response + * @param {_Response} response * @param {string} referrer */ function renderMessage(isLoading, response, referrer) { @@ -43,8 +47,6 @@ function renderMessage(isLoading, response, referrer) { } /** - * @typedef {{ ok: boolean, status?: number }} _Response - * * @param {{ * consent?: boolean * referrer?: string @@ -86,7 +88,7 @@ export function BasePrivacyManager({ action="#" onSubmit={(event) => { event && event.preventDefault(); - return actions.sendConsent(consent); + return actions.sendConsent(); }}>

      Use of my personal information for advertising purposes

      From 3de01f6b65a4de722084ed6e4cbf21ab075335ac Mon Sep 17 00:00:00 2001 From: Oliver Turner Date: Wed, 27 May 2020 16:18:41 +0100 Subject: [PATCH 400/760] Improve a11y inline with PR feedback and update tests --- .../privacy-manager.test.jsx.snap | 212 ++++++++++-------- .../src/__tests__/privacy-manager.test.jsx | 27 +-- .../x-privacy-manager/src/privacy-manager.jsx | 14 +- .../x-privacy-manager/src/radio-btn.jsx | 2 +- .../x-privacy-manager/src/radio-btn.scss | 2 + 5 files changed, 148 insertions(+), 109 deletions(-) diff --git a/components/x-privacy-manager/src/__tests__/__snapshots__/privacy-manager.test.jsx.snap b/components/x-privacy-manager/src/__tests__/__snapshots__/privacy-manager.test.jsx.snap index ad3107ec4..b1a4c4642 100644 --- a/components/x-privacy-manager/src/__tests__/__snapshots__/privacy-manager.test.jsx.snap +++ b/components/x-privacy-manager/src/__tests__/__snapshots__/privacy-manager.test.jsx.snap @@ -29,6 +29,9 @@ exports[`x-privacy-manager It displays the appropriate messaging None by default Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes.


      +
      - - + + +
      diff --git a/components/x-privacy-manager/src/radio-btn.jsx b/components/x-privacy-manager/src/radio-btn.jsx index 81389ace3..2a0f1c612 100644 --- a/components/x-privacy-manager/src/radio-btn.jsx +++ b/components/x-privacy-manager/src/radio-btn.jsx @@ -21,7 +21,7 @@ export function RadioBtn({ value, checked, onChange, children }) { {children} - + diff --git a/components/x-privacy-manager/src/radio-btn.scss b/components/x-privacy-manager/src/radio-btn.scss index e49c16830..a25b433a7 100644 --- a/components/x-privacy-manager/src/radio-btn.scss +++ b/components/x-privacy-manager/src/radio-btn.scss @@ -37,6 +37,8 @@ color: oColorsByName('white'); } + // Since
      -

      - Do Not Sell My Personal Information -

      -
      -

      - CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. -

      -

      - Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. -

      -
      -
      -
      -

      - Use of my personal information for advertising purposes -

      -
      - -
      - - -
      -
      - -
      - - -
      -
      -
      - -
      -
      -
      - -`; - -exports[`x-privacy-manager It displays the appropriate messaging On receiving a response with a non-200 status 1`] = ` - -
      -

      - Do Not Sell My Personal Information -

      -
      -

      - CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. -

      -

      - Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. -

      -
      -
      - - -
      -
      -
      -
      - Your setting could not be saved. Please try again later. -   - - Continue to homepage - -
      -
      -
      -
      -
      -
      -
      -
      -

      - Use of my personal information for advertising purposes -

      -
      - -
      - - -
      -
      - -
      - - -
      -
      -
      - -
      -
      -
      -
      -`; - -exports[`x-privacy-manager It displays the appropriate messaging On receiving a response with a status of 200 1`] = ` - -
      -

      - Do Not Sell My Personal Information -

      -
      -

      - CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. -

      -

      - Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. -

      -
      -
      - - -
      -
      -
      -
      - Your setting has been saved. -   - - Continue to homepage - -
      -
      -
      -
      -
      -
      -
      -
      -

      - Use of my personal information for advertising purposes -

      -
      - -
      - - -
      -
      - -
      - - -
      -
      -
      - -
      -
      -
      -
      -`; - -exports[`x-privacy-manager It displays the appropriate messaging While loading 1`] = ` - -
      -

      - Do Not Sell My Personal Information -

      -
      -

      - CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. -

      -

      - Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. -

      -
      -
      - - -
      -
      -
      -
      -
      - - Loading... - -
      -
      -
      -
      - - -
      -
      -

      - Use of my personal information for advertising purposes -

      -
      - -
      - - -
      -
      - -
      - - -
      -
      -
      - -
      -
      -
      - -`; - -exports[`x-privacy-manager initial state handles a change of consent 1`] = ` - - - - -
      -

      - Do Not Sell My Personal Information -

      -
      -

      - CCPA defines the "sale" of personal information in extremely broad terms. For this reason, the collection of data by advertisers who advertise on our Sites for the purposes of measuring the effectiveness of their advertising is caught, and requires the ability to object to the use of your personal information. The data collected by advertisers may include online identifiers, such as IP address, and interactions with advertisements. -

      -

      - Our advertising Terms and Conditions limit the use of this data by advertisers only to their own business purposes, including analytics and attribution, not for commercial purposes. -

      -
      -
      - - -
      -
      -
      -
      - Your setting has been saved. -   - - Continue to homepage - -
      -
      -
      -
      -
      -
      -
      -
      -

      - Use of my personal information for advertising purposes -

      -
      - -
      - - -
      -
      - -
      - - -
      -
      -
      - -
      -
      -
      -
      -
      -
      -
      -`; diff --git a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx index 8579e70e7..14c63bb11 100644 --- a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx +++ b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx @@ -13,7 +13,7 @@ describe('x-privacy-manager', () => { describe('initial state', () => { beforeEach(() => { fetchMock.reset(); - fetchMock.mock(CONSENT_API, 200); + fetchMock.mock(CONSENT_API, 200, { delay: 500 }); }); it('defaults to "Allow"', () => { @@ -72,8 +72,6 @@ describe('x-privacy-manager', () => { expect(message).toHaveClassName('o-message--success'); expect(link).toHaveProp('href', 'https://www.ft.com/'); expect(inputTrue).toHaveProp('checked', true); - - expect(subject).toMatchSnapshot(); }); }); @@ -95,8 +93,6 @@ describe('x-privacy-manager', () => { const messages = subject.find('[data-o-component="o-message"]'); expect(messages).toHaveLength(0); - - expect(subject).toMatchSnapshot(); }); it('While loading', () => { @@ -106,8 +102,6 @@ describe('x-privacy-manager', () => { const messages = subject.find('[data-o-component="o-message"]'); expect(messages).toHaveLength(1); expect(messages.first()).toHaveClassName('o-message--neutral'); - - expect(subject).toMatchSnapshot(); }); it('On receiving a response with a status of 200', () => { @@ -123,8 +117,6 @@ describe('x-privacy-manager', () => { const link = message.find('[data-component="referrer-link"]'); expect(link).toHaveProp('href', 'https://www.ft.com/'); - - expect(subject).toMatchSnapshot(); }); it('On receiving a response with a non-200 status', () => { @@ -140,8 +132,6 @@ describe('x-privacy-manager', () => { const link = message.find('[data-component="referrer-link"]'); expect(link).toHaveProp('href', 'https://www.ft.com/'); - - expect(subject).toMatchSnapshot(); }); }); }); diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx index 0393f882b..a8e2ccbe8 100644 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -19,8 +19,6 @@ export const withCustomActions = withActions(() => ({ sendConsent() { return async ({ isLoading, consent }) => { - console.log('sendConsent', { isLoading, consent }); - if (isLoading) return; const body = JSON.stringify({ From 033436875290bbd9e20b3658db44a7012a327807 Mon Sep 17 00:00:00 2001 From: Oliver Turner Date: Thu, 28 May 2020 16:03:42 +0100 Subject: [PATCH 402/760] Add component metadata --- components/x-privacy-manager/package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/x-privacy-manager/package.json b/components/x-privacy-manager/package.json index cdbb38d77..9380bce67 100644 --- a/components/x-privacy-manager/package.json +++ b/components/x-privacy-manager/package.json @@ -1,16 +1,16 @@ { "name": "@financial-times/x-privacy-manager", "version": "0.0.0", - "description": "", + "description": "A component to let users give or withhold consent to the use of their data", + "author": "Oliver Turner ", + "license": "ISC", + "keywords": [ + "x-dash" + ], "main": "dist/privacy-manager.cjs.js", "module": "dist/privacy-manager.esm.js", "browser": "dist/privacy-manager.es5.js", "style": "dist/privacy-manager.css", - "keywords": [ - "x-dash" - ], - "author": "", - "license": "ISC", "repository": { "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" From 122d32242400a50696e761702aa2576cb79f6512 Mon Sep 17 00:00:00 2001 From: Oliver Turner Date: Fri, 29 May 2020 11:43:57 +0100 Subject: [PATCH 403/760] Sidestep CI snapshotting issues: rename 'stories' to 'storybook' --- .storybook/register-components.js | 2 +- components/x-privacy-manager/{stories => storybook}/data.js | 0 components/x-privacy-manager/{stories => storybook}/index.js | 0 components/x-privacy-manager/{stories => storybook}/knobs.js | 0 .../{stories => storybook}/story-consent-accepted.js | 0 .../{stories => storybook}/story-consent-blocked.js | 0 .../{stories => storybook}/story-consent-indeterminate.js | 0 .../{stories => storybook}/story-save-failed.js | 0 8 files changed, 1 insertion(+), 1 deletion(-) rename components/x-privacy-manager/{stories => storybook}/data.js (100%) rename components/x-privacy-manager/{stories => storybook}/index.js (100%) rename components/x-privacy-manager/{stories => storybook}/knobs.js (100%) rename components/x-privacy-manager/{stories => storybook}/story-consent-accepted.js (100%) rename components/x-privacy-manager/{stories => storybook}/story-consent-blocked.js (100%) rename components/x-privacy-manager/{stories => storybook}/story-consent-indeterminate.js (100%) rename components/x-privacy-manager/{stories => storybook}/story-save-failed.js (100%) diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 2da5f11fa..57fbd2465 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -6,7 +6,7 @@ const components = [ require('../components/x-gift-article/stories'), require('../components/x-podcast-launchers/stories'), require('../components/x-teaser-timeline/stories'), - require('../components/x-privacy-manager/stories') + require('../components/x-privacy-manager/storybook') ]; module.exports = components; diff --git a/components/x-privacy-manager/stories/data.js b/components/x-privacy-manager/storybook/data.js similarity index 100% rename from components/x-privacy-manager/stories/data.js rename to components/x-privacy-manager/storybook/data.js diff --git a/components/x-privacy-manager/stories/index.js b/components/x-privacy-manager/storybook/index.js similarity index 100% rename from components/x-privacy-manager/stories/index.js rename to components/x-privacy-manager/storybook/index.js diff --git a/components/x-privacy-manager/stories/knobs.js b/components/x-privacy-manager/storybook/knobs.js similarity index 100% rename from components/x-privacy-manager/stories/knobs.js rename to components/x-privacy-manager/storybook/knobs.js diff --git a/components/x-privacy-manager/stories/story-consent-accepted.js b/components/x-privacy-manager/storybook/story-consent-accepted.js similarity index 100% rename from components/x-privacy-manager/stories/story-consent-accepted.js rename to components/x-privacy-manager/storybook/story-consent-accepted.js diff --git a/components/x-privacy-manager/stories/story-consent-blocked.js b/components/x-privacy-manager/storybook/story-consent-blocked.js similarity index 100% rename from components/x-privacy-manager/stories/story-consent-blocked.js rename to components/x-privacy-manager/storybook/story-consent-blocked.js diff --git a/components/x-privacy-manager/stories/story-consent-indeterminate.js b/components/x-privacy-manager/storybook/story-consent-indeterminate.js similarity index 100% rename from components/x-privacy-manager/stories/story-consent-indeterminate.js rename to components/x-privacy-manager/storybook/story-consent-indeterminate.js diff --git a/components/x-privacy-manager/stories/story-save-failed.js b/components/x-privacy-manager/storybook/story-save-failed.js similarity index 100% rename from components/x-privacy-manager/stories/story-save-failed.js rename to components/x-privacy-manager/storybook/story-save-failed.js From d3d33853f32818affaaee9ce0f9a529e3928e0ac Mon Sep 17 00:00:00 2001 From: Kara Brightwell Date: Mon, 1 Jun 2020 16:27:15 +0100 Subject: [PATCH 404/760] Revert "x-privacy-manager" --- .storybook/register-components.js | 1 - components/x-privacy-manager/.bowerrc | 8 - components/x-privacy-manager/.npmignore | 3 - components/x-privacy-manager/bower.json | 16 -- components/x-privacy-manager/docs/ccpa.png | Bin 86550 -> 0 bytes components/x-privacy-manager/package.json | 42 ------ components/x-privacy-manager/readme.md | 46 ------ components/x-privacy-manager/rollup.js | 4 - .../src/__tests__/privacy-manager.test.jsx | 137 ------------------ components/x-privacy-manager/src/messages.jsx | 78 ---------- .../x-privacy-manager/src/privacy-manager.jsx | 119 --------------- .../src/privacy-manager.scss | 63 -------- .../x-privacy-manager/src/radio-btn.jsx | 31 ---- .../x-privacy-manager/src/radio-btn.scss | 85 ----------- .../x-privacy-manager/storybook/data.js | 5 - .../x-privacy-manager/storybook/index.js | 20 --- .../x-privacy-manager/storybook/knobs.js | 35 ----- .../storybook/story-consent-accepted.js | 19 --- .../storybook/story-consent-blocked.js | 19 --- .../storybook/story-consent-indeterminate.js | 19 --- .../storybook/story-save-failed.js | 23 --- package.json | 1 - 22 files changed, 774 deletions(-) delete mode 100644 components/x-privacy-manager/.bowerrc delete mode 100644 components/x-privacy-manager/.npmignore delete mode 100644 components/x-privacy-manager/bower.json delete mode 100644 components/x-privacy-manager/docs/ccpa.png delete mode 100644 components/x-privacy-manager/package.json delete mode 100644 components/x-privacy-manager/readme.md delete mode 100644 components/x-privacy-manager/rollup.js delete mode 100644 components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx delete mode 100644 components/x-privacy-manager/src/messages.jsx delete mode 100644 components/x-privacy-manager/src/privacy-manager.jsx delete mode 100644 components/x-privacy-manager/src/privacy-manager.scss delete mode 100644 components/x-privacy-manager/src/radio-btn.jsx delete mode 100644 components/x-privacy-manager/src/radio-btn.scss delete mode 100644 components/x-privacy-manager/storybook/data.js delete mode 100644 components/x-privacy-manager/storybook/index.js delete mode 100644 components/x-privacy-manager/storybook/knobs.js delete mode 100644 components/x-privacy-manager/storybook/story-consent-accepted.js delete mode 100644 components/x-privacy-manager/storybook/story-consent-blocked.js delete mode 100644 components/x-privacy-manager/storybook/story-consent-indeterminate.js delete mode 100644 components/x-privacy-manager/storybook/story-save-failed.js diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 57fbd2465..b93d545fb 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -6,7 +6,6 @@ const components = [ require('../components/x-gift-article/stories'), require('../components/x-podcast-launchers/stories'), require('../components/x-teaser-timeline/stories'), - require('../components/x-privacy-manager/storybook') ]; module.exports = components; diff --git a/components/x-privacy-manager/.bowerrc b/components/x-privacy-manager/.bowerrc deleted file mode 100644 index 39039a4a1..000000000 --- a/components/x-privacy-manager/.bowerrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "registry": { - "search": [ - "https://origami-bower-registry.ft.com", - "https://registry.bower.io" - ] - } -} diff --git a/components/x-privacy-manager/.npmignore b/components/x-privacy-manager/.npmignore deleted file mode 100644 index a44a9e753..000000000 --- a/components/x-privacy-manager/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -src/ -stories/ -rollup.js diff --git a/components/x-privacy-manager/bower.json b/components/x-privacy-manager/bower.json deleted file mode 100644 index 7dab1c91b..000000000 --- a/components/x-privacy-manager/bower.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@financial-times/x-privacy-manager", - "description": "", - "main": "dist/privacy-manager.cjs.js", - "private": true, - "dependencies": { - "o-buttons": "^6.0.0", - "o-colors": "^5.0.0", - "o-grid": "^5.0.0", - "o-loading": "^4.0.0", - "o-message": "^4.0.0", - "o-normalise": "^2.0.0", - "o-spacing": "2.0.0", - "o-typography": "6.0.0" - } -} \ No newline at end of file diff --git a/components/x-privacy-manager/docs/ccpa.png b/components/x-privacy-manager/docs/ccpa.png deleted file mode 100644 index 041bf2cdb02a3a38778a9a24e30af65d526defc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86550 zcmeFZg;!k5(l?AVxVuAwI|K`^!Cit6E&&F2cMrjx;O@cQHMj(q;O-9ZNQaboVm@34%9>3fH_#2Ertw_B{sVT2#|L4BRQVAEb#N{Jd~_Z(_t}Admtg z&ho|8-4{<7>oI9ZyxjNgtuq2KmSqPD;yq=?SiA!VhON{*L#5U?6o^BB5%BDjBVe; zjoz7!A*JqOe;3jrrAa6GFz!`i@S9V%mhyMA$~(bw%iT|Amm|}8J1&sfOtIN4VG)_T z#K5sO%cMdnxQYAwp%cu{L2W)Av|XOmRh?~jat8*FIJG;Tb+F3*mutKnH&+tnsVUPu z?l$}#RqIiT^(zJ$CrlIXe*)s4t~j86yI3mZd)aY0@R;9*40i$vX=uNsi2wi)$mjqs z5tv+Kk^6UnUtYf*hnsoT_PJvMOUcu_MQw?*yK(I+%s7s}9Dj}Hw)ZWH>01sP_C%Oc z9AB)xrIdOQa4?rK|1!J-QI8Fo^J+L?)`SEhLjf^PqMQ28d?Y06z8iuv2ZC@Cg0BlA z1xnTsKEsdb9Lk3r5EMX*j)n`xjP55#4%uu-pamNtjIfB@5I|o|G#dcD!ODZE-Gu;x z(}mFNLIJ_^!?YVhw82nq2;c{j1K{H*=<^{DL?yq%n!vMBpbeoR`cvk((IUtMi+@Gd zhO6+G$zd->T!Nkx5fT~v&N2DEn5YHqMnr6qXrCi0K$;b0J|r4woq|xeVaS814^h+; zx#`t}z6Q|j`EX9rL?8%l;D6TDelF{V4XcDm%O`_o8g7*9rS!cRMh#IWK--^QQa#5? z38k1zjph)y6z@aSW6(fui3Qve=1B;uxKv*Ae*ZqsKFU7*HKY^ABk?0{3z|T{YY2sL zJu5E0CNdFLsC55tqX<@<6z>%3G50Z&F^F9O+{glB`RbpG!e98gP+GBDAzQJQ!)yj! zjGt=qc!(9C&HWt*gSU8^zUX4uemq4#}R%-h8=0aKrHV&;jL(?u+P) z^+qftsYHp7p^NYxhLj?P_i0aL0R7qw>`*3hrt(ZBgF zKVM>9N=cOFCQ|Ce6*Z~UXxDtcDBNN|^fwX*D&wkL(%~c+CmbaJ-v@lF`4)>QWf`pU zW7@uev_RRkw6?;b*rD(eZYOXgIRRcBL_a|9L(iWmny8&vNB^RRJ?D2QwxhD3bf+?* z3U8s6h7c_;o0m9=I@4l7RZ3rqTFO=`t5&A2Sb96H>}1TBo+Yv_E+o?_`&M39F`%+t zR-tLF)LL>Y;+@oz%xBQ9)vo*!^JtC|5|S<1B3U9CIbbYbU)XHcWLCM$JbsndTW3+X zVrFlSGr`AqBP^vzd{_$_j6d$@+VOSs)lWVmiP^}c8u zdn_Yc4{*wIFI&3w5_7q7;-zS&8#MYcWiSTeeq?GfH#Tb>!yT_oT{V`jHTk@&HLr8~ zd8$IRTtnxFR(ma1y+SqfkJI{Bvu%rbVBNy*_lgLcuP0W^T+7@pyDqw|R;|moCZ4`e ziMJKERV0kW_o%JJr@Sg8dwi9S$DrrK-^cIltw4khTuurOYnv;*`E0!@L@9WBtjuf7 z40;MpUJlL<<1PY+kD=)WIxWj0jorM|j`xnYKM!0NT=$yJ?9Xlt-QKxBZ%iGP>lu5)qvsuKk zam<@@!XnE;UzQeN8fuH+O>BnQz;m1o`r@YL=6qiVSPnDA$c&pEl8A$+T9W};rU46DW4>uYj5Pd!HjcOz8cqlUan@@`;=e@-s+ip!rcC^ph zl(-GOZAjWxnv^ySQ+vfl#p+;SjB9=Kj}I?KH>0mu9yFzjrCBRZ5pJdDv-bzjGkGUl zx0@U#=8qq7o%#8RTyJli&mHyl0w)aGHhed~(3D+WOWZ}$FgNS-`J}ox??lWb?v}-9 zrdRUnTOT?cT6LUywcmt{MmL=Dt`^yPtnoXJ-pvOuA1`a3j#<3pjP5WAWanVJyTl90HCObQJ$EVx7$BE8)qttfl_O?+> z5C<<}&R5u?e28;1A$GDsXyJB92>aJrF9EvA*>uBiUA|i-1YRl`5XC2u9+&GcH!=tEU2M5>Fp!^tMH>?lt@3l_u?-#W5 z7egWH_vFWKuyO<2*c|2{JWhUk%6r3Zkgtq}D*R7H z5mhm1Y4Bau*um7)*3rVwscx=M70hbhQccTAOJ0u8*v^I-Xkuq%%IszX`qKo0-;EDk zv@vx8lDpY_v32Bg6QKO71|PWm=P?T<`CnC>tOY2w>NzVKQMDLvr-Bol9QA3 zJD8a9sfbJdt2y|a0HuYK6Nrz6#nsi7*_DIY&cU38jhB~~g_WI!ot+6>gUQj|)(Pmw zWa~)vHUR9Zem?>}
      }-(z6Ig~W|9`ZD9#{||Y>QhvcyBq$yK7>z%EB$ypr!$I@be`x}ODu=48 zCoTrg;2&KOj*{>T&ck26*0ujH)%+Qk;pQi)z^Z?Afqa0nt4BG?#-ozwKQ#WQ@B4si zP<~$b!aurzZVYBeJHRENneh)DIl*Z0OiyiC|Dy|GHDGqw#_3rMKmVa4KCoU7EY3gt zqYHgnV0IBT@)J1b|7PmH4Nn5rs}tw{&&(87whmSUIu1sY(*xpav*UvLb;3kZj(B)h z;@#V7i@au)zGJ&=1`mcnHf8fs77M=rx$s7JAiUG*vqWRLzD#n+R$ut`EhGC%29JA$ z!n0=@vtG041dVTu{%BmLO8dR239WVlV{e{REUiYlR$OgG-n$%Ki@&TD5G7oX5tAk8 zEu)ylUlYVo%SvIk<3vw%80;32(NhM(xQVRQZ}kyl4aECxhuCchW+o#O$IsgzL&C7p zU;3hN&An-H{nbaULk=NzVjrz=s<2{N*L}WHKlSl$|94Ac%XvZOb$yf#E`!E#&aB=9 z)Yuhbtwwu0MzM_=y#w0vAk z4sDqm!Y9+#F&HY(6wKm|b}*UPO+G-4g5GR&+N3Jdx!92~EwiKXxl_r>`=hOezU(qs*xQF5wwt#&-u3C>>i0KK z`cp)a{f*7Mq)L|ox3~w=O1Agw%rXS}GI)h4)m`mEJ~AnL7!MG}BK2y35t1#MmaDr+@@u;g5pxSx$}*FMqf;Zio#wF zWQ@Sq6cz+g!oH>c>xVuU6>3VGXF45nkbB$L->Vs+=c{sW?9&|CIEv%S;mZ@6_`ThJ zV{d0<^r_Ur*srVUIhfFo*xv15c@{UF1&BH;ft!SaF6{EztkXsD-~(dxu{4@Ha z(W%%}@T6Ot4}_tDaZ?7!^3YZKjF&p;XGEDw^6Z?H*>TTgy>w1>tfMw>>M5{3!UnY( zh+~c1_J_!;Rj9aYDwBicvRj9o?VM}gHh|i7uhxwVddrRM0{ULlWA09R8IMK#mj#$d|$JZ+qp(Uf7Ww*|JU!AdHyeuOvdk`aE%p?5%2$^xQhaxPGM1N-cIjelc zGY8s9yD?(f@S!UQA{+L4cqmJ1DUT-5nzam$CfkQQbYDtczr~X+H(JRM1O5Yf*$Z*9RE=yV=T@)K(*KFjXt(}7(Rv}0oeam*A9aNDa(9Go6qO-`zs5O47IY@`t#1+-qA0imjo;8Gr9DU4SME zbmJhZ_Ia)2O@+JFyJfvt#VkHCLv$xsI#!`TsTj06Wdb?U*X^)*nVnIkv&s{?@OvKx zascwup|in|YG0c)nsc{5q$Hkbe&`#|2LxMo9t91O9pn(~P_+~c7zhbd*C^Yw%*gIRRO_516>^T6#rxE7Ig{O#ek0qxGObnKd zh9eYLzkQg;ffwQTAa=-s;Hq!LpkX6Kc!7WOy|wZLX9)jJ)A$DN1?dt;7h(DI@g2WiC+{-W$Trti0fN_du_!jTqk;hgQ1mB zcmc@JKZkyU>e+X(XD2>&7S-7hikTR&jN^Nlj=?7zc+Hy@CsVoc$3*Oj`<(f)!InCR z3MVo@vW&&;apv@GBD3mWJHr@ySPdD!<$A3&KL0rrrUaH6YHT%BM!l<(RkY z;)$KU?N`-Qv~ADqFBqndq9FEbBEF^QVU9>7o8u_=NiVo0{h#0H=O=7hwY`DlQG~Em0_*P? zG-MgFUm4l!jdL!LHCm6FF8+3$*&zM#8Hl@4`aaPFMMmXbhzhe|ay6!V=Ru7@tSEUE z)bu`vhVvdiJRfe$bPSWCKvzFlnhb9kP3 zXeekUMM073i>3>DZel)+XNNHHeTwRGOUF>~7jw&0sx-30ogy2y&PPbjIWHWzv2HbO z-U%fu$l(&n;4H>EukOL#v{En2hfiLA&u0y?R$-2&HzWamR>2jz8Cx(gyt|+|#U;%z zD0knS!fm}HL8cnR!+p{P1+}emSNAyTLdDOa1~jRE6cfHQR}fz7rc^KDvELrDx8`zT zTPV56X5HkdrIqcci>gK?82+Llo-)j_OAsizC;+4P@iL!eJDlnnhx)LZOhEpI*|hkZ zh_AT|$(mcK3pWFjkn!Ik#k~*|+jIr$+#ud=O`~vg%m#t3yYM*;w>E332BQ?Tr*oXG z(O&SA6g-e=ngBM#EtHifaY$$6{VnVdto4`Fx_x6!H2~`s9t%|yaB*rn(Yl8!=`32#Y-6q!XSj&ff`dc9|+8yTxbbAnr{u3%)cmMAP5)YT?;RqezC7kt8LCZXrw z4SR!$TIeikT&1!+sfuSD+eKpzqZo3k*|sFbK6Ng(6E{Qv#cgD$6Eo%PJHjACe&uXIoR#ZQM=cPSvYIm7$U*1m%m3S^r|Rdv*V4$GQ^G`{Zn{FraacMSwSWOQx0q{%XizZqGu67IEw;zF=yM_?e z{_8QIPeBTXS_y_hdy@mkv55*+y|u4n>qlPA26>8t8B7FHt@VDY+1*sD3gx{67F7PJ zA=QW>CtC#!*D~{rwjxnuG0UUmtA=m0=a2$6s$@%zq1%Iw_J*)9?=r93k@*6{Sq(`k zn(jr>X>0JH-Z~#_%dn4~b`1)6hzl0rZm13_xm)>6vutaV!t8Z zci}$8|IBJ7*CSp20dk%DKpK0J1VXoKtB9wO;I-vBB(l$$l#b(Xj>;kz@uE=rfgC?O zHR&x2)DMGS6bgHVkflJCpA2R*zBfCYj}KknP|BPiU;EIJB&s?idGmX*Os%7S1+5r< z`fg^^UA)nl^&DBU^$7p+Upp48KfX0D6m+2u#NR$btj!O{v%g-Qm9TbSA_j*k@T9<2wvF^ex{eT5g{m%8*L%veIKa&F(RyEd z`}{Kaxivol`bNU|NdtT81Rt3{aOza!{=)IEusxRpswU5I=lIa9a1PcO@sVb>U7?@* z=@p|~O0x4R5^wcxtoT0bHJ}EctvXo;WHDO-gWN<)Pyt~kXO0?g+{8k_)zWHuxQ7yy zpCnH>qB@`vZYK{U3n#Eab?(0C-*VY0eUkL9yAHfTrg;@HoTux{tZaWBx_!I}c<4Qr zg(8*eXFDEf*DQ#@`yoG?oQy6EYI{mEMFMEZO8DDG^3;qM;cFog&N;j8sG0ixfLre^ z)#yx3S0W?b#>7Qu=y9{2??{&lh*KxTxLB!F$V%F~CU7m|Q83~7_?^St<<8z-pH`-v z)}^>$?XZ3I3I+jlvAqz6>vTNsJ4v8_5yn^H`c^jij=!T`YI1aACMk|RCe)dZ!~M@% zYEExYGv>Y8bf+urM3St^>$V8-)p%$i&ih-E)|GNZjVY|~7bZ#eme`G#2P9x8T>>p| zv1y96%<=~sX)+d_eoI|!P`u3{wTOdyX(41=IdN`@WO|3uU`1;+!pzzrrMyR z6bCj3f%;})!s=4S$y+8v-*LUEDyLGtcJ|GCI|GFEn1gqWdJ*L1`5qUrveqVU4HB^m zHgwsb*512@C=Fe8eJ|f*93YOLA6eWs=1wY~r+>P-cp^psHB8ICoFwYO@)m^mmheRH`q9v2%oKv6?{eVwU)ezPU+7`>Js7$s)!!G~#90({W$+%`vy$sf zk~|UAj5j?Q?v*qR*vIR|uS&z3K10(+Rz36Ux*pm&x?x~Y zr`sYC^VI*|u%uBnbiAw}jt;W6vyR>^fBnIPkl-hq$;g%_)&XtHYq3#LyOtpM@& z!e87}+{eGHZ)3tR9wOb1Cib9UL3Jt=iJ|d@57~!fkwlLmdNpZgDulF>kxeO?Pva04n$tr*hbv;4%^RaW?{kAccC*`y;8OIv+ zzNhWh%H??|esM#xh%CFqYfk$iGll(qgz{@!^ZtCrM-|m*)m4W*uS9wz?)EThtzi&j zvyfVuRRQagOQQZbJ&vA~PMLZ4Yd<7la}KwfXa38q3aNj#EO9(CBtob=zTWY4zV!Pt z67YH%jNNXs_ZC{@v5)#FNR8sUBo-PwJ%m;W*XmBm=pALQ^{LTzeUrX7jfHEH_u?~j z)CAU67hE0)X7e?=2Uc&Bs#n6qa&SMu+{8ka8TzlJh`%JcHj-49UvO;HRy}iO7S5Bs zA&OE|hljZtQ_sgQ180i^FA2OjgW2m1OfNZv8t zpo(e+*S?>#ne^GDEX>w69V@O!_i*S)lNTJoI0U92(m_FdqRJ-D z^eYgXq~)!YQn+%IvL7Y!_^D)D9eDo=kO0_FY&{h}kr@G;_W*jq5G(#dqRkq#Trq3& zQ7lF|)d6I@bsK4`q-cYQF0Clyl|eQXuTN9=tHgDms+%@G3LJVWm;t@qlVmJr+bOgT zB*UOljI3!=H}N;5jYEQYAt%$F@{F{u#p?1}?7xcU#A@GRXP;XjDX#KejHjs>G15m2 z*(1D{A-_8;;gd)-$e0X&!1SIEKd1OD`f$Yu8~ODvu7D&DDGOsdEQ9VZ-HV8mF9D=9 z;2~*`$@|(q=aY9Ae6i>xt$&U0|C3t2S-NhN*oLb&S>xnR{)mH)4An7naq!Jujocv2 zOsBo>N!=6sxM%;YI@VISiusXhVU6|%-L#vu;12#KE|j!*?-1`)*bsm_cJEhBPBe`C zvCe$J?T}O>C;CW3i3TVT$PkSqLYv0vK|7w!)qP#h34k#u*OmLx$8B4V&w6~pwgntx zaUnjkJ+W*bTcWeoy^C*-dN{m8z-q~6y^9*3>ILUmr}IXRz>Y+y`|dDw6UxDFr{6WC z8l9+WIw_lBUfPkp$dV`r*6vy`LFlZDflp4ZPd+GHk9^GAgbEsB+82G?jv1f4ix6ZI z(9Gr81pzEcJMELO2Lp+mlAzBIziM7$|xn15DD>U z*=u{^xY*e6LWP@v=M;OBOtdi&QXdEL07&5&qt$1d`aO1!R7mo4i1G>yWwv1R^_J{{^uJwwnyOl|KRTN)lUV)O;1bsVU+@@I zupeej&m$BJJ-k9FSV6=XT{TB7hCu)jiHXsFz)=8SFieH$JH{&|*%jP{xBkUJZyRd; zei)odT;}Ki{%2jXKXu76c=063k)hDY1asA`J3*fMVqPqf;r}wjzjDV$=oM9bTA2#6B94)9~=W>!H{V8V2b0vgT8+uf&R39V3Kl9 zTvOOTIePukw%|a<-`o1X)5RhGV2e=EeG(@BVIVMn{|zw6s(jA2OZ*R*nH-TEOv1K! z$4vPTv=W943`qvWvhDplcKtUBtPBArz5fG@dPVWV|3krHP=83UgyH|s zWPe%i|C#JR=i~qPDho^MnQHgEJDA>Kxc)t?DhqQ#y6NQccwG_|=;mUN0S~-b*AdN9 zHDk)@RLr=hJ)E;%upEw8PzZ2?_i>NR^rlL~tMR$eiLeSBDGuLp;(o$;FZr3MCA z{LZ`Oeb@6R%Q4c$E;j`yU+)9q(e{s`(@WMTr|PXI1P7mUwf$%mvpzA#zPPW{@8j(> zc^EzZJlzYOq1Fkb8fyiKR5%Gmu+}FV4~fAD!cUj600xfXO9#1(0>#q zM&M2c;b-U!l*ga7>f95XAZ=&)V+;Toxs#W2N%+~~YvgB@hw0X^Q25?YaIVJSm z6rWV3O@tv)z+pb&Io2xv3DOu;I3>fw=l0vxYNdq($Fm4dy?iw#BB4|qNl^RLYVGNc zY_gsgsl=^c`%<9igWdY-`(k907k{vt?hU}WE&DT>@5_k{G#>u_k9BML#3L>Y(S*Gy z$}OV}X1xc3%uWzSV+^Oy2qT6MN`VjCdjYrmuWKd0!pvB7xA0lMLjKI^6b1tp_V3;A z_t~^Nfq6%33rKR)CoMO5&1B34-BdL9X;J7wRWfpCYwhOg1KERbaVIU_gDh&fDmKg2 z8Y~NTi{fIE-og{_Wvy`D$p}fUOTGnlS-zvirVqEj`Sn;8T_tx$Cg34{(sGw4@Z_6^ zJ)F&ff|Z3vCgj6-6zt&NRuRi>8Z4}pr;L=1O}qBn7}@h?=>tWA9*1De*Qc`v+advY zRFBNN*FY&K_D;7%x`t4|v$VZb}W40mo77E?uJ6?v{a%9y7SH5theyVJ5d@ zd41yK=NejenQnNBGut44F>>3DZxM?LT(+fPc6|*cDa1KPIbHn<2J!NyQUPeU@X6$FO`| zI%g+I1$UbZ26z@ju5L}swQBaKw$UYVu;kn@a2*T|md`qhz$mp!CuydaJmtuMr=SEP z;ba*Sj%QxAl=GtRd3AjjW9r95l_HO+Jf3dk>p!^HjLYi&6%Lt7OMS`k@}rw-!_pjs zDmV;6@aE^Y>}#S^K_{7R^mH>L%DdFQ>R)h>Uu?3K$gkwyJ)*~1+51v%=TE+^Txk)pP1DOYEN|Yy{nKVE-bY8v$ z>?t#Uz1;DUXRmc*wceC+y1XWMbl8zf0LOQlDd6nAz1}vZ<+pcgtOleH$ej`fEbl7h z46hSF*AELs2REXyv@XY5^`~WaHeiJKr4HBYU2q1E{gzU*O8@F*?~{9E@)Y;FP2vPY zB7;0QW~XfWfI`TtFPP5b&Fp=1VwT4B>aG2D(+D%}x*K(HP&d(;dloE>--Ed(M*d~K zm{h$?O@E+HhG?hB?SP%>`5r3(=e>SglUl2`snv9KjLxsGyHcZKD|-V9S#)}ONz5sm zGM>B7{rY=HN&ds*YtW$OsS<~y7`qClB}CfqNA6K0QBJ=7Y0F9Um01)AKigwi-2Nl)%g8x6DKRl%WWvF&*2 zwCG~B1Uwyx?CFT-yUzkfGuxi5P12(63&!^fRXiK_o5{;pis-2vy%w?6O*%DpLL>9dO zpH)x4SBxItO5$GyhJj8Z)k_ukP9%CA4(has)2a)%OnNW+aI6nk*9TaS;(gMYhp^!c zx;vQEa-SB0p76`X(EU7IZ4{dwJLsRN83S@q1>lt=rmA!zeNf~n%Nx5WUSusx^dhO% znuPTUP!IYeiIRV2vJs%InP?0uV@|Xp`P4Wh#RkKedtTc|6fs>^aEAx&LC%%y3Tu zlpve$q#+TwrLp~wmZKyDen$$ zTAx&&t@5Oy|H{(H$gLZg?z*oKeK^mm-XhG#30(TJ^lpjNxG4sm%Nl(2Oq5{!&}7af z%y^FF9Mx@kd3W^3jPvTWJ+;r#or7<@A}^uQ2ppss zs5gjxO9*x`sR51m2YY(cmwq3V=}wLZ0&2=1+tr06@#~%s=gW;gE0%B5sv_1%>Qs~| zk(M~m%sfodGr3*xR7@4aU#1JX#_0xjf9#LMLyLV+5v@!nmj^y}g1&N%1c229V?w`M ztmeAA&RNF6tgQ;)S4xx5`h49XF3kpjKjL#a&reL(-=V|2PU^4+MV zBQ64@@rU*sNsM9g0E#(;$jhGh41GrT4agh@O^~)YfhbYUrHXWH-G((VFAR(t^g_=@ z=yofY(CT1O{M)oyZ|^%_}41^r(7*&k=%cNeS2hp06f3ibp61`y?BG6~i5H3VIM5@SZfAgsXFfvr8%4=OR_V@P0Nl0sn8A=4Vr_Ldnb3CQcY36ya~C9awD3##AU~r8fVr+ z4^@a6RKLsks0Fw#0lb)}qX#T_)pRE=C}4Ev(~<=p5OLd$z;@qE??N2}u)$>Fh$7<( zv$|?6c6}8-hi?DI#L%vH?d-0XcllApQ1qq7Z518LqBTjLR(h*%R=;T#C;~?zkZ>`K zML|1XLQ%oIzm48Tj<1GyK>V_;Sh}2#a)OI%aP6MoY&h&dK;~P1s9yg`grYtq>U$~9 z=u~6rFL5u)ee+Z~-upHTLT_+ILMB=$d!HyIg!mmr6gS9-b!XY-`PgCZ z2F|Xvrce_hB10!2ZaJs9GDrTqeXx?9q@f9$dFMh7Y5_QnPRI)^#n#2ZHRYTiE;%WG zgq?Ow%fpbw2&xwqwY)v9LwG;%tFL9_q)AeH-*q1vefqmzHrtt))y$9Bl&f0hHC^7F z4Crp$-^030&>r`L$@axGHXoAE;q-Ae9AgNr!D~1Rtr9~j8!PjXI6-57!6vO;mWBU~ zznh7~QD_KJO7YJcHtC`E&5}(TbKQ7|w`ktrjmGy=>fD>`KgR$A@ZT_r3 zFMO*&NCORUUq5$pdxliIT%oKHSr%wXMe_%;TotnEb=;BoTL5|aXr8%5SXXs0IK}q8 z6kwP>awg>jhWaLJ!)b-jMo2icPJ0jI!K*#+_Pd zHTW(ZtAw`Ffb~n*N6Tv^`qz1)%ZCD4&3D@>n)R^w!#6!vDNjE%3kH^Pra){yedGZe zS{PiO4S2Y$?=$3Z(~OTl(?7TQ{(9HsuRgAC9iXX|7I}6Qx4Z7*@6(szN8p5|Qe=!kfx1+4@>VlV z5c^mLPsA2tq~gFe%WxpxhA-;T6iRoWXq-}&*$2zHpba$9<(yY^+iLFfZAC%HIp~Lp zdqXw+$dOrls(d48TzFPlED53l#bgb^z(n*X1d4L)Iiw!yM?(B*E1Xf;Mn1+BzK6(? ztfXHz_ZZM6^-2yoibiJ}E))s0WvPf~oHR~ni`rW9KDo-GT0D%8^aBx?qHG})tUv|! zn7vdisZn022R~pCM8Ts1A<(9ef2Pq>N*(zU=1fdVY*(tRrVumGrKwPf+WUF9XjaDp zxQ!%#V#XG`JD#c9@Ey_oIg`V*yja%3LlfY9)^JEmV7ChOr96Jl&98sB)F2pj6uASB}>5(D2MLa8d%?*&^@)p^Z2 zb_U|H#|z%?P3)s+SwvyEdWnpP}MJ|AH{x(hSb)jS`w>JSlPr(($JPIjMR^!C{m_~UEK zFmg_kr-;N;BQ$e~LM|X%lxG68K|d@%e4fsqRnbDP=3xksg@@}M6cvMmiia;*m*e5{ zzRf_+JDVD!)Gfgl350>F3yh8<*9zI-;#O*Ub`zb%7Gs6-|J(-!I0;i-!U;NOvwoEl zUG~*xpBiD`khvG0V-~P>b1TvG(;74Jr%ubQ)j(jri&}yCIRa83|LZ$ z%m8|zAkC12d@jn+;V3}A;vyp3DI?afMK`G_)VfBDf1sCEA97poMjfi0PWXd@1gr*w zZ=h#=7cCyjUXQV$GM7{}$nBVKw6vki&lm5ODd^P7x>AFcAPezvaV5}Gu>A>MFVoLv zD-6qHr!==-!zLBPAmB=1dvJyVkqlwG0r|H*U|)6|yG$r37_g%h`njv%7zNl38DCNS z6@4+f(rn*)WUH)N!T`Se1BTPnPa8Vo2UQkdwnW&PhrIH4=(yy^*&oSnm2ivfBY7=T zsvEYhW$L+cZD^(k>3e#Zuwlwb0P`R%E48((t%`w28siGDk}Xb6*27kqUITGj+nmMy zU&AfOw1I?nAcK%ZEYUDNh(RryqK3LdJajb?4aa(~gY&^<`-*R)n0$&k@>5AH=0D#V ze19_^PndFE`d;PuAwf(AB-t}^cy`2-5bStw%)M7!>~htHnD=(*n5&u{n0w7&pGI4r zr&P}$PwsEM+SoIN#ov~?cU_yVcjUd0{y|6a=sw(chp7z~ zvhF$R0RK!q{c}YbeH+bHo~pLhPbEu2->G?YCBtYqpl!E6xdizbi%#RoR~gA;X?P(7 zk=HmStvj+kGB&B{+xvb z|L=h-;tp1{vOve6D75J3F~ByYXONR<*LqFRw>TG=X z!`PLA$-*A1lOLG%bOy`UQ*CHi5Y|x;f;M1+&oW940PlN&}`93z9t!d z#i5IwxC#IckVXoBQ(a0C-^>koOM##cYyYsAaCTN=r0}pbhau+xj`jCi*$(+`L-l!soq;EJ0I$#V>17kx{9BhQaVDfNL@0hDq@I9jSu4=Z2Y3 z8VdE&7AHVp2B$}g6IHRwcSiM!g;~0^^)renU+@bEm!CYEH2ka2P$r;7UT2YzTZK~K za-To-T0RS>^P0@U@Y-&Q@&ONtlRYzNOqFi>%pspM9otB(_su~Efk-9~?C)2CJdJ2X-J4 zjc=?M_CQ0><=JEt>C_dQLq!_f8kPCwpePG|HOS0?4#d!PFltqL8rDOw)}=t&r|AUE4nxhtmk_SUG-m!D|kSm0JX9-U6enWSu5ZxaOvCJBKo&@rd5 z-8KkNh+JJPm__y7&bH}1Um#x#t3`P*>dEdKVhOIo$M>x}p9fBKgg>D{;lUB&dwqEf z>?{|Y>;m=5MWa0P6hjg%egC{l$@uOSJS`UQf}+!T%->q7-}#Nz*RCb?(iyZ~Z>TJ` zzp?T;{Z4uIBgTJc0X@~xg@NB=gRIe#E&j`R5OLWXh~n>TR?B0LT4W2UdL8KdDqx!Y z!%I``OOn(|YR@XV=Pc+O*%C2Ss?jtt76#-w$vc30Sf{Yot`apMGv&ANpHtIjUS2}7 zB=h73_^(=<{)Rlk2m!c3lCw64;KN@HG*5>P2+{1Ecj*nU!k`!3R-eEqYv6g@8Po;* zHcZg?o(0|xpI@Lnl*sG+D486zvQPddr_ZDTD=FOFfh12QQIT#B(*jYz2goZjg)adg zh-thw#b(yNH5$v4gh6u8(Ql#Se+=;CP>2p#rcrPZH5l}PVyLzL*y^*%DW$VhBy{JZ zdt@)r+>lm=$D$)403UPysH5FY8mi!2o4$~-wRF>*(IbE_lZ|2|w~^1{<%i$%t@d1N zb03YAhpy3iZ_NRjH*xi#eP(fC+n9_q0hR{>UX)^h4b>RxS2U~u%JvUEp@z|kl$Z-a zr6c<38*V18ArYl%Lk8$w+~Y{_pVYWHY@8F0#oqfhUzVb0euA}s{*i81PD$hrw^4yT zw>Flpad72$h7oq;oiIcIed-ixu82_-Ii9-~xRxB;Cm!C5D$E*ej71uQr_UHu6q1Ml z3QI-Ccct# zFK%faKmtd8-qj(bFyFLvwLFDv+c^7q1Y)2qilTFOZJTj9BA5e%5rffs9`{gi&SoSY zXTa$YSuwP;`lVwm-K@~l^FGADdnmpmMDbkSpAg72oF!yJ%nxMOXrnV-&i)VP4y*-z z@IQP}wT2{lZ&2H8hlUt+2aNHolvumbV?}u<37$TSS{&j(>16X7!HYv zvIRN(j2q@{f(@m-5*t4E@DhmQGW8wEfqgP^DojsS$F<&;Ii|4}=WKIwRnagt#aD6Y zYyqa%9iJ(>a?Z9=I*v5U%H0UKJ3hw|FN3Q0j(UfBFte@TVSL z5+761pmcgqFzE{^bP7j2_S0Xm3Wcu20HELJt&hD2EcGk7~Kg z`~M`o>b3CKM6>3)yhFM**It$o`d0xC(46+@O~VhWXPHq;P-Wq=5{hI4^r`N0IP+ z0; zP{KP;;XYOxuOAjMnS{0Z2wuay-?`t4QR&vWj5EIMEDOqOs>^{oF2Q#E+JhL( zy2r>j!87c}_Ld)*d4aM%nKj(>uY#xgFd$kxBQ5 z5ke)&BrnZe(C`)I!1us|^n|J7L3Q}bgx9`3;HiQOx$Svd=%CQHlZXwO%R^aWzT|?% z*5+FC@9_S=;@h9ml~arQ1fHdnkX+dQCmQ|dt8+Lob?*|30sdzQq3KKc=wgbz!l1KP3N}>it_hG^X*896IIvE&(wc zHZShquS<1-F-V8+S#Il0q5i#GTJ1EL=&l}xJyn8gI)#uICu)SAtnQaSH~HOk*ei)ek{_OESnc5bsrMVn$8^D^zcO5Jx)#K^~uENIS;cTZ@u@d zNAdm(1{#HT$14nGa*T=oV0zRER-&M;*~sw5<&exKLWtwx=+wb;Sj_8|CBfUj$R((A zLgZNn%xeoo65h6baPR@Bq(V~1JTM*w*G;)k0Rr-dj>}utPPM=sp}-)Ajh^l3JA)M# zPLmTekiUFK-wzS6^HQ;1$M+8}POVg|7Pu?veHA7*P8W0)Ua*=ECYui-5ebk-XD1Qz zp*n-Ufq!s6VEgJ2bCCoVQy@Pt^4_xO_b`k+u5~gYz{40ZCmJ>7rTT^H!MPf+##6=u zswD7q`%7wj*5q@1KKyVsndtO*fjj;EMtFnAnr$)?ui(5re8c#Z&fCg=<8P`9{pVk- z_A%6*^{H4b2uyOwo(_T1XgR@;*RMy-w&M7n6WE}(*ERz@mf$c%tnlxH9rs{D{DDuZ zM_0*+FBVf%_l-(L&`Rh}2 zrDn;qx2qZ$ol*(+zFpvQ*@NPQ4KK)x<+AvV((TuJBuaQ3?jbY{UTGYNd|yjOE~+sQ zq@J|*afBU%`2aAsMBc&UAVMCsh3H)?aPE&CJnA^;_A6_D^35dQdl;K+tsc$aL9Z?L zpQL%%6Ox=s#b$}qCy7W)JbI-4#s`sBvcn)J@2maDhwSc9uIEA0H7734CA^4J4NK@7 zkRsefpgG3Qt0@}OtWhmNIV`t*N_~f)h|EpAV7&Qh!@oBN?9fZ>%3^zpz1|En4}7?7nr}9e;XS2<+Ovx44eVs|Q5AK&s zj_&#WETllfA>YBAjiEtlRd>u~FLz_3UX=a2ai)UnxZLfcl}_^cel`v=v#TiZnLcA~ zU4KO_JWU*?6s$JSHmKWD0d;;D1sqYXQ!8YKj$e+x=%D`Eaag;>)bDjpUjG=A9^rpmBY_o%l(!VZg&QduRCP8@4wjT4&{8ebSXEM;XG;R{@gxG;Wjw8QQLFL z4hBm~!qTr30@wLq=UHw0v3Zx{**owVOZFRC&>(c?RMg!5$5jFYKXPg=-&T_E%nvOn z$hg&B;+RPGpngi}a_JH70dvMdeaDlXj9Gi9!(e3^w3u?V)+;sh;N{KV`*C=7 zdYuE+O5hW7s$Q(kmjLM&>03K(u;O3a*&yO{&T7BWF4xk3U*34=OFsBj$MaC37Izfz zgmlCUrntWYz#2-vf&Q`zI8|WI+jgjRb`-hRK>#2i}iaRZv|F36YJK}8-dFiVy}E z#S?H_6lT9o=S@6L?i>2c(Gl)-Hkjx?T-ElL%Z4?4(T`=$86Tbj(_C}(okpM2wCgEw zrb09RB#BMp4bGe6EqybMS`CiR;g9$$n5Q6ST1}gc_D!R67XRsxdT^RJZA%dF+AZzP?`~LZ+=jwT~%na&A ztR>~_7EQ(>TAv^$dI#i7s!v2ICR(Po#t(JskOXocH zXzMZ-TvwzAKB5s)GJBS*b<1cy)x!myEbE-5A=q-UTufTOM(p>MfvYDb^?eck4#S30 zphUAb#{-Lfg46Ge^*`ay#7EP}XDMjb=oQtA7Zh8arfp0wgLC0qzmtnwgG!%O9qTRp zo_|@^oPLOie`eVW$#uJ4%B#77_tuJYB*s)))KnTdnk!X0!0eZMPorjUS3f3I^gf1J zj>`92zFuFk1eZd2k$i8n zB8A*$In17_JwD#QhhaaD5i z6l3xVm0}9>v80T@Yk_1}qO`+XoF=PgqYRwa-TbY|HL?*2;Rxs=Na5g0iVM}YO3g+F z`<07Ex4?!`WGLKGjYbJXQwZFp3&kv!iX96o4}MTqv$%$inlA}2p>mZ9?Y2j4zI|G#iK+1 z7JaUmk>sMJqEFssn#k2LR!~K&l$Y2agF{aTGi7o~kh#wT8xqsevr>_4=h<}Dd<-fF zc43`Y~u#|#cMYF8iGGfejLIjdcu8Vt}-yHi0Rh;1V+wTaYEP#!T0azv~6i4 z6c)`)6Bk?a1qzqd27PqHRyjg7iajBdRox_#wWldYEz`qv64kb|eXydp(x<|(vuk2m zX-KX2Ai~gY>C;rHSG;W34P3vEN~4OL55+Jm%$FZ%1=u_Ds2W$D#Q!xPlz9_Asez{g zoPw0#*}ciN&d_$Ny!1e}JaI8gsz&D@1ETofH0YLO5iM-8jF#B~gSv78$A_s__m1Yv zD87@oyY79vA{{17amEj&g|1QiA*1N=0((q9J4_VE2XO_8&-09F=L$JpJ>pCgXyzM! z%9bScjr*`%q}xkApnn(t1!oWaDopj93CFJwd=ZKfU3ph_DPVp$=Ez-c;q(;7?q2ir ziI_YajNn5w0$L1eCJ=V!?@pHqu9!p7=!lfE1*x&+W#-DOZvw1&SQ1m`p5N{-FqfGH zVOb5gMDD|j89`(ft1*l9`w)+ZX@=!uIY%kdbfWbhj6SCeCoVl9z9ke0G=DgzL0_}L z@AIQC-YB0O3#q<56p614eT2Mp<|=*+uul4xG)ih^}SAdUFQb3tI8QcwAplCeeBF z|HTZ=OGDftQt_<3jKd(svNkHz0Nx+$KfemLAAl2Ow5)MUWzn$RQoBgvW$l141}&QR zx&q(^cFqHCeZ9Ii!@1Mm;7p2?m6oW{Nx`oO3#?=Y88(x)a8U@G+5DFI(J$r7imVyr zPvm5M#z}a5Z3!sJQYjw@Ahlqy4!y;U`UX`snF7uUM?wij=AnuPBm^|X5#l;;PfB$b z7=A^epRtm2JL=_mAd(~AXlihDWpS8iS1Ex(uZ+bWZa+$@PbZx}9jA3!9s}vt0;W!& z)sAhb%@hV{ic(jWfVw*O=*b_eR)j-&aeW=2RwEMnC0AU8N04&Nw^;)Z2leEgkFVxMUWMJlK>X~JnMyL^ zWj`nyn82OQW!*F#t&}vtQsoF1^<1>uuj|molVV_zp(LB5cZ&&$uwe3knMRCt6zbb> z+L7OJEZsN?>Bo{5FC2Q#+lK4BPdL|1S zH!fStERM0Brce0W+ro~2vj^NOTm8cLFvk1fDdEQ+kQVjnijBxUY~U*&*5+OkVEV$cq$LBLaFVyPC{h@&A04!#og}T%zG<)^-dplLiu}89>{de86=LT3Cjz#K9Z-9%Ct2Nq-r7gJJuD zbJfGOa!s#Cl{Ja1vHs3vbbnXCu#R4pHIE6PSC$xPULRi$t?dzWQzH}P$Kr^f9+;th zKst|Y%Qoz(Oa3V+S0P>5M_e?YEvr+!Xq2?EMdOUEcOsit_Vz9I&@f4ZyEpp4SAEo3x@wQjH2_}^GmjUt7=2Eih{^j856 zH*XQm37PC)!io`&(aRL$z8MM_$S46%mgih_7YNgrH*HgCMUchbWuu5d5h+9eD`H{{ z6kHlR^mKdHDYCUd&KM`?Swsi)X@~*wi`waQiN=s9$d7yovjUuW`GHC~iJ7-S1uPio zidE@!`2FYm?wbtol^;Wqtn3NqYgn515j^CXw8*rGM8VBn%nA(^M8jQuC-0p|U!J-b zT_8haAe!8zf@y3kobCopP%L0Dg_}&kLP!z;FjL&LNM?!>Y~wdnjWR%2)6ob^X26<$ z{j6ZWqd9fqyZ@K?up~&KEb5d083Hp2p7Av405x(94CfgcLUUULH!=)|$m|+ZlO1}z zk$rz~3g>5&@)Yb1vKI(t>@)ejkA-QKn)`Zk zzd^fW|7KitGotEhuvN{&{M4#zluW4fs2SANE^opw5{yiTpux$cu|FAMC4736MB^5i znae*M;Q}qu8`exPP*_S!n9&@gpXrg_go`6a1B#-8LL!ukx^s zR6s)*;4Hdo9f~QQ8gccmOqABd+Ov!=1Mya?&W!Z=oX!!NtycTEK%!b<6#crvNuM$H zz5#~#;Vvy+od=YP9dB$~Z;b+^NhdP-k{LTx5chqI+v>8$PDfEBsefwP^|=9&P4{co zz8KPdF~IyAosT501yvNdwPQ`6l8RwlmZ+E~L`&KqrlmrjNP=LW^jYh(AN9%ZmlskT zr)l%el05}oUv`$$N3Ok!YLoQ{RZNyeElqL=!H>_7tE*y;SS)0L7yIU$FNJg}g_dFY z$~q*uFq;T0FXl{N6sdRgLcnHnzSk5TqWdC0isoUqlR2A=UAhv#$Sj?(Dhzr9&p~G z$KS!y|J+F*9ZLpJ+@611iqinHqNcW>Z*=y_H3Ba>R5EEiM%bT-A*cZi8g2vwaEw0D z7wf}b)+wMRX~64QG$P(# ztK?ynWHF<5=zWM%Gg)2ArDg@0e(I=3MNPn?4BFlx`7ss|R$gT& zBXLKtH;!e|&ntkjs!rB*^nwM-2%YV(2s91MBKBVCJ(+d!iMSvvY@|K(F;&McbvqaS ze9#4@`CT+o7IPH&+d_Kdz_K+<{n43^aK9B=k#A{$BdGLb&yx`)`!TE59-Nvfo1AcQ zxTW>S24y{rpo)CYT>vZ-9EX7CPPAy;T(tTftzzSwRR@;4mhs1ql!zl^>OS#m}0PAUEHV*8X~HGJ9a`l1$AcrhXjO##5#c z?nJ6K{=nS7dI%Fo%7nRr0OGeJ3h%_1v3|(IjU~ISg$eFs>Lt^IGYS~$$=M_Bm;)Pa zNfDHy&=8)mN>}1Kbye~he-)gZy=xV4AGHxajuO?3#=LxT%E@H+4H|@yN5?Z+h32tx z%Rz`C8jTRb1%Igf-oMEapsF=nS8qsrVjN^^0@mO4OD<^(s{&&_jF8=T0=Hms%R;9F zDf3=Dk5ix31M4twak9W`n4|0r3K|`p)@I){4JMBgl}l$EpPJuC#+RIwPQ=R)$4-7L zAWPu3Kxxy*=n(c-A$J`e$@;p?Q)kwa@(t4>np+e4J*)wqrUL2suoM3|V5WX4?kRi_ zc8$aEP+v`8R&jBz_gwFnY|-cCg(0RF-GR&)eugnB&N)0?C-sYF{i@kdol9|i0#&E~ zOi5FOn`0^&X82>UUjEO2hD+&XSxbOX{cS#A zlhCa!3y{iI*}hscZlx*E0EBbi9!a3>CNf&JVE#0?Tsk+eV8NW{p^nl~akBs;ZTSMTLpytQQ2hgrx21m6lv(4lF#N(W;A#x= zL1E1ZcjpNjvANoUFZg;8{a6y`K?&FMzma#3k<8@|`+uxJ&>;NzkcOi5C*aYXXDA+f zcFpJmbX*?0TnA%~&QsW`=?_|{W8&wM`Y6Juh7>~hs*vexrCdIxwiWwi`(S?|x`bM@ zup%Dsa?BTt@h9a}#BmrH>65F=)a+Rwx$LFz0Hk9|?)`Xl+Fox7-fF}MoxYdN=|s~8 zM__lk549&bh(Upl^dAUa^DQtEv!m_|!VJ`F4v{62dke^->IyK|#BCh>Dxl~S{KWFe zJPgfLPiD9>#G)V|e^S?yC0F&>S>N-1l6&-Z{AcwB{AsNvqSQU;BeE-D)opr=4_bg> zjYRISC({1JvuQ-0ny{B6$j~Nq5_t(T@`X-I!dW@E)Ql_bNzFpQ?$6^on78w_Zf+kN z{r!%O;~U3jxz#jvTo7C}bA((Ar!`%_0qq{P1s)jYvqd{xo40r<4V4LOB7q~+UXVl- zum;e6k(W&v%j^scsMSG#LL_9!$ojWUPj>JfB){sC-4dDDhySTsg$8CBx-g~y<7!P8 zY5<4YJqBf>kx)L1e)XE?-*z^f(y{9rw!@sr@DOTpmk2p6X5HFbDx3V7TcaJHB}wi} z3SRZ;P4Rlr#K?5C;*ErYn&F`dV&UMJWHPAhMXaWuw#)YWFfQ9!eviR#q0!LFBi;W+ zW)g^oLpmMBbEe{wFCq(78POX4=!+^yZY~_LNc%LVy)(19)P@l36x#-t3YS^Jhn zx!_AAhQvOGJW{)5rO@^%UoSF2Eq@##1edlC8%ubK?4%Q7^O{Z4!n@4#zM2`&pNX57 z0Sks47M@hp)q(27Hj^s;#C}@PtcYzu7(?1DxQ^D&DbImZg>@di=^VA^cGO(77#76O zN@4@R$Sum{y@oKyWA%$giN0mgW%!g2yNDj0BZFocM!7u30uV~j{L~pwFssQv`A&^l z2z*sV6_-Xu3j(uegJH4YF-8AKuSoF1CYLaY`K(>LEx6|#sqfH#_J>PIN%%@StOQimfA}h!;IvqtpXI74(49OYU&4PM%wd1(BONUJOjL{~m@- z5e62eJ_92%)54)Ec9(E*g|2mVUp zD{`|Xq1T&orl3X0YpkOnmcSmiW4Ze=-eZ(3W3^r27Fx-QV2iwGfsXgU;tw}w>(^*6 zIyG6c;Sgd8znI>lFR``Rc8U?=gKXb(_GreTXC^jCTH3O>%^xQxR3&TpR?UElJm53a zpr-V&)Es~uj+l4$H7hWWoN~870|8yA`P!wz z)^M2N(LlC9uk}cd!TuSF=Un^@IjAh|eg{^@o8k9`cpXszn)nRjkbs_UKU#VFZ279< zO)Bin>z`1O6Kw(a$Lyc9?YH58IE29tx0~U)6z5g_%-Ep9QK59(6UQ0<{hJ109P$sn z_G?c?(A|*p>UrXD&qse1=V;3tlG6RbI84y#69+h8Cq_gAnMU1QuGG!(YBEO>pP)!_ z+X_>tH+((u*Z8pGVIz>_0}BzuMb)_>HzT1U+$a9NFA8uL586UN3)QCf+KU^v9H(ne zzz^scG8*1M9MzNwdgiu%K@4C0>oc80cb5(+fx@%Cjw8l`yh2jbvk34?jV_v{$Em^_ zd(7D}M`cIZb)C{CV*#^%qgxPB9^%kI2~#EmN7wD5 z*;abgWo?|Yo|g!9l|%1^4i;{hWE7UC z<5+KJxs4Lb2;JR*^4N8ap?OB66G$^UUQP5>BlefR6Vujhsv&9!hUx1WZfGxzr04c- z&o=kUu-;n0@W`HHA3-2CvT95XBpWs`guJe9)Y~kiT?@{0J>m#cCox&8-pITzUy1K^ zRrclN_i7%UEiI#SN9~yLxqD%5^YBE~ zmzUS=YC1`53H#PI#=rvN7mV0%h0c)5?cikt?BOF&)~osVaKKdckx6Q*zSjSn;WOp3 zA3F^!JG*Y2f;w6T`W*sVfqkG`rwTOGrXXDoVI>HV1;XU?a}RSDB$Pt#q{vSYl8Dl% z*@EF0y}(%w>-3k$rW|0I4V@&vkCWpnykAplS`t!nE0b}6% z^ox^K#4m`ZbsD`2maLEkhxvYyznh%9SM`e3aBx?tCvO}7PhekvB^}YIBbaQ9$KavAv74L_>*z>XQ{Kq50V1Y9QP)M}2!~fBupo0A_`P+;+xBsW) zpZ{+1EA#UGXWSsN0({79*5zFVdzZ1F?nu+SK5R&%kqG94;d_K*2}SF>NsSW!;w@O_ zoyX^Eu;&UcU7hoLuj&Rj3niV!nn%;w$s-?~JArcj-#zUyWtDQ4Vc9B!J8JvaSLy9- zI^Tqg7PBFnBM!5a$}`E|qn{dr2}kPcy>J1BiLMD3?F%peW@c-{MosU7&Fr)RVeiW? zMiwum)^M(|ujku}Iz2Yk07z%lL1fO1iKs7Huag>}s8tzE5`pNzrNJN~poYc=RW)vxkNFd34f5BS! zwwyL%=pTbWd;VQ5Zrq>jY!D6TgFu#JJv@0puR2@j$|K`>JEv5ySqXg=d`%b>1ten- zni6+0xAtC~Uakb5BUEqH&9Fz-v!p^28|Za=C?L#%>?9zsR$9-N!s~slW8c;mPn-7p z#5(Czj@It!8$WFrN|YPQ<6k@D7Jo$)H9p<@E+@`f@uic^zvb07!NuqL0HgVMJ>mA` zEl!mxaPl9e_x{3^&#+LB%<*_z?st9)ooYqW?pxA#+cC2cKB>DGkMy4PrlF!OLbgk~ zq`aW_004-+ip}F?YQ--0Dxc?~pUjk5AaK(p-Ad8cBpm_L?~j7+V^TFn9V81c_+0_VIsIz^+@%h8^H-|gWC+P^*5R=FVawKLv8QvT z@u!m-U7*1cs0hlqC$5XW4mNJ);ne`7vJ&Sa~64S zn!8`x_6^+j%~pYd36(nSJZn{(nzS#ME^SHSUx>j_j!w_my`op0{%W9KHmK|91=IH$ z5>c%F5|S8F?z)0QGK6T6zwve-W5aDjUlo^T&iy`S8g{3$Vk{I0P9GCE!MTCcjy&Oj zAAnzQ%<&jS`Q})th|>GstXk*zY{%d{0at}4ht_Y&Q8(!3Md{H|$s+NgI2QMyDCeQl z}*|zn^VNaTp)jfjYrp zU3&n$y!KAu1?coMzg$o3omqo>BH8o)1=Qag*uVUP)Mu44IiuwF&u2`7jbFaSBjbL{ zuI&*oH}Dg)`74e3S3)4*x57L3F1{*N0}Ib)_u#g4WmJ`Lnh(d*ko^#L8|^WpX)KD; z0dFDUaTzkj(O`8N3}W1EzLem^5u)pDl#FKr=BBdQpl^<5;~UH;nC8G9wAAC#%7oi> zf(X51O?JaNzJUuaCzv6NT81tlq$c|)tf+SDERpz<$9`U?C`ZQXWRP!iz{a|!p3#cq0o@U(8x#9ynyzr*{W%8L z*>n!ULN$5%Ec@($^#Xjy4WaB1?6zU0jU+zq_{#y9ITvat(>WJ@n&o0LSZy<5GROy) zM$Zm6Gn%b^ftAFlI3YhmR6=^qPxd=t#R$yTNv)`tZJ?|{YZQU7Iq zm+Hg_n=iB=L|(_<2i%-`HmSsppqO;Mp3hB%K46aGrMx|z3Lv;n_8I8YgR>#*fJ&b@ z+++BGkpE6Wr0D!#v0Ey52<14g&@xYf?l@mW?d)W@jAtAE1>F{Bu~E~|TYG%&;tS3! zwM)vuoPg&kd7CWm&#bIRw`0+CjtN!*aBKA7vdyUnHIdK7Rjse}tMr(eK&f2v@&dQh z)TJ3JM@fWlXR~hm4zZwL%2ss!@0wj-ELsnuId811h6SQfHV(P*-&TQDSKt^94`cKS zMvzJxHVGW~K^Fqf*O2UE-DH-ioiEf#-)~ zWH4b(w8Dx z5cyX{n7O>(vPhU-XojcpCeAPa;+8H(ehfGUj8JNDdnT>u4FsS{${_hq z=%cJ{EvNSx;C>N`Z=2*P?|V8lSp50~4hK-S z`4R~~MvM;MAd}~RL#L{*TP#yeY%oW}s6vMb7Ska|5GPw{*~SGoSHR&SBgrVY|iAW+>5exSmHrkR$pxY&-AgbtMi zPehx<=}_0d!D$N|C^3`MsY$3h<5+|HX#6FOYM zhloFRY235@yi$0Qt)Buv`Ln?Xu__t+33*e%WbkthAe7oLF=1~5?W&e;2!AAW{TYQ0 zZ&}I$*NZEGYsRV8(b?HputLt}?-Ccm7(Q$A!_P4X;bC7!@ebwLuP1w_nhoZ~tuZ|; zz=g8sc(a=9a~>e>HmdzSXoI$_Lk3><2RKDM{kQw6yhDre+g>pBn7Q!1sd{apBodOb z;E&!f)mriEOnS=0GsoM2k{a=QFJmsLZhR3uH3gWt;>E_cV75VFY)SC;E(L2(s4_!n zdtYL~8)*htu{I2RDJ~C~QT!+GxDWWa$3TZ3h#8HUvN6XeUtAn;y6A9@p{1)L(nS9V z1V;*ze+x!>&pV)Ia$6>%muZYWpalztiqW#`V)jQ$TpS*DShU_QHy4k>A48nOib1?Q zS2`Z5v)yJt#yVGlH$-6@Uk+)8-mF0y!ubh$SWCxUw5;@2u=w%Pe-D$YY|$qGD|^DN z{}kEIn)QXI=v6u_AGTpY)p*xB>%g`XW%kDx94nPfnV^6=i!ZP~Wf>Ip z3x?Bo7Vj52PnB9SKeEMj{4m5QguydDk0050ME%Hqt6-W0VivreA73s{3%?W@BlFYx zWz>HS%H%9#t~OVP+4h_=yP_QLn&u?>IDp0Zhm1OQ9!!jl9^uw{0ebN*4n^D(>+Xo3 zEgl&3z|@%5FjtS$^N81-F@3S}`ZSeP&%H0^f)WmclQ-Ik%?i-8lso&jB!KCaRRM?x zB$7)MApV-JO8O=Xt%`wx&+y%fHAVSz0eDKzNGkY-RUnoLFJ2t_Nws9wXdFF*9vJv? z>-)ri#=F1jde#Z}>)U(t(B(TNWSOubl-+%6;44xqXf%b#e?`sjHDp6(5cuRP=o3RnfMAOesRKt2u7J3vCFiYt6Hn2)wg3Y0l77L&;Pv`HnkIS7aiHXt`etnFe z!FBC&2GX!w?>d_JMT^5NW-K{hlWFoNA&pj}tFPQ6?#QBr6xERAuETL{L=ipHCE$785^oS(RwMGzJd^0j zMuR4ukX4rK${Z*D*8)Z)z#aC`eKa!7ki&na6VrhE;t&37ZQs($k!u9ppWLxkfmuL2U+=V=H&hsbY8iReD zD}$h$55uAh4t771H<5Sznllki4lS@b7tdv(nJ8;!e(`p7+RQr^CXY16Ekqd-Un2p} zNRFJ8Uw~Bj%VHGkGXd5z!%rDfe2><`KlWp&xn@yI3uc6x?YV^4U>Vyv!6zpp_! zrUYMY_xqmZM@A8I+hL(OOQGsc(oz;6tFT1t#DmnKviTeJG*PF20aH++U{QPPn+R(2 zlj|GAEd|MQI&RoEZcT$w@hbJ4+qFLod?ji0YUOVGm0qdEW871KuGKFGQ)2Ti-z(<8 zXpFwo!?eXeoI!(wy0%79jBQYEg5ENP0pr2DI8#oHPp6&1e2D3iY#2G+O;n{10%vsQ zi~3Ka1T@SN)`mY3qzVznYpJtA!~;O4MqTB}KhQ%6!M}fux!vB6P|Hy&syOE3i7BJi z%|<~ID=NFlqOatXHy&vjH*Rq%HQfyanC_tj-x}|_sArc{e2i6>$uf8vKU=Bw*WR`1 z$Q&@H#=MM3Wz=BnEc>m(6r46P`1wpWMBt2rvFXFnB6qeu3G0367F5vZsaF1{gtSz( zft2LA?x)8`8_ltp4ik1w!}RZNEf}(|lpQ>XN$YhOQbU)!P}r1h7q*OIHziUx73UQe ziV@2k8l$W-z=}H_*Zp726}-mi5Qu7bFOP8d35%pa1-y$&O@kYDlt4g_RoWuVazmI2 zOi%`~*?vuq6rZe8&+OPd6%{dQ0F$Y`h`||$e1qt8+Qhv}YKG`?i+_ZU#tsM4urRUJ zp1i)fhb1|aTGf#lTLhE^tbd+DuOX?$nvXwxcjwGhk%QI{!fOGZchvj1H9N*yx7Cm> zU1WS(IbL}La_kGRrQYdGcjBH>mw^YZ{7erjrVia){`0ahsIuC7cWzxy>FsYUW0_&2 zGheS(Q;F)DkU!4n> z@>Opa(mj^(4_zqv8K&QYUSKp>kz+E3yDMP4x_^Q#6Sma@cJgr1U>~Hd zjv%j+{c#XS7C==MypIAp=2v}l-HjHU%-ZL5jnlsNqEdL8U6A@o&3^#!ezZ>flkHCL z!Z8X9cUv2e$3*E?i>UbAUv6OgWYa;~#GIz|xRf>GeuWn!z~>U%HZb@;TmC{I(c~&j_yc46x4Ki>1OM>Pb)`Nx98X8G z75EJDz}Wk)SRngQEdR2O7#FFBT$jIAljj}DX{RDXcEi?D{M?H`B>53FOr_ODbYsrQ zr2(?f9fiJ_d6butO>}o!>u~|%k~DX|Cr@%%{_}9w$fxTwZim`{51508iDaquDW97- z^Ve1sVCv_^dYy_EL!7tFwe79NBm?vsoQYpSSTJanol9=8;O;G@>GLxR*QT zSp14eNH7!bo4@D#Y#0C7HkiyF$y`v$#tQ=))Ln*)!8|tAxo_k|Twjm;tuuKbjtgWm z_HI{D>M=Q0>e`wr^Sx*y?Y+aNDVzpu&Wj=3W*V8#Dh;rQeT1K*mv7i)#V!4G40P&? zN?T$V%R61*DfrMA42eWE4jDofNxn{qe*N1+)kzwm(WloVzHXokRM_on?|Z>0Hy9$J zSx3NawIpW|YtC#Je2E-*Aw%)#IY>BPJCw|FA2s~ZVIT!@b%?_F*oZ_ZgPmod zndE%b2clp^(>{YAx_uLXU%G^HhEt_1R#$)tAE4FPmvy`t%{z1{h>FBUduSyjy2RTD zWEQjQheg6xq#Z0#nCBttz^KiCHQjYCTNKH@hJC#clvg%0HQvP)!D32>*g}W26Ne+L z>W~MC#p~vq{R#br*x?9$_%)|VXcN`p>ELt7ncsO7n`Sd60xPILn(dNRS!osc%QO<& zw_Dl50Z~oSLFDg-$F`6>nqyVsY|kc*&Jt_U^K+$khd6lIMAT;+!&6u6)fviCx%(m- zelHHq{fI_{#lYhAH*<<-rw*RnnrB2#?&}h<22*d*MFKSwp!IM<(iM2>wICyK96i7fEE{tXBg9=$L3;XcXCGVKd=p4*QdyCh2!NO`eWT4WIb! z1P>bPEPrQW!|j4fL>AA$^HL;7nPCiM_s{ zSi3P(WSluNWID4Vw9vSp#A1|AS8xazVV3F_gAHo=P|x_lN`8KrH6dk?p6PzF3?$lu z2s{XNqR2@zo2y$5M)ep=t+v>v=WY>mqGEx0%5v&5>D@PE4Y5PnI?1IK9I}i34p$Ek*Y}!)yhh;sv)gbr zf|qdz4hkE8cHhu>rc;ad6->?LOf)}1GiHZ0Rk2#ZTb|hVkJG>~>hdBHW0E64pQ4WB z777_ra>l!zRy};pM{8?YR^MBGz$nSi{@Yz+9331&>V(zmTFjucq->BsZZaKm4UB~h zp#W-{C8@B&fqQO#RzIEB@B1eJNbzo=gaKvDxt0dwuZy9ZKI_R_` z0e%reY|BqX%hSNSbxIi**g|@;@84kRdkEf1&ImoSBhtro0`B;L3WKKauk}?VqVTNd za$0>H=ZyePiE==FbO^LsRZ_6}j+H;&C91Paz4^!MzrzMt>4`U97Ro;y-D-7;Er3n? zWZ_GO4}))yqm=p`gaBiWmz>kp2gN4maPL~!Zr`Hs#c}W@?yROQzEMshIys0Hj@){F z0&7Dny#hedbg>tL+|Hpz*gOBHr}TMY^2lO$!V>vx|9H){CgV+kGF1MnxW^!%NB-CC zmqEoQ__m14MDqqqPEURTG$7`{#t zRj+JTOBJ-aeO(jXpGih;99DYPPUQNLU{KAxyzdL}->yrDttD6a8unRz%oarj-B3AIklTA?H+MTTQPBpF#sot$^ z(iAGWM&R#x`iybw5P@g0E_s#p`o#<@Z)Xv-m4NIsj#>|#-|$$MIhe?5Z9}~sKl1=@ z#x%b!ZjHsx99YGD2i{y8EY2MqCO5=TPCq4Z)q6f(b)fpt4*zo%CB-OL>&ABmy9+vG z8Fw!PpA50*Yr{UrOuMaa0I8l$hWyDduO%-Jv8dnZ zHRA1zjaUh4O+r}x8)g=1;;(z_u^e3rI`1+xJN_<|?)t*MuBd z;=RqRijpH#dOb9oo_~-n%@grl27f48NqFhrYTbzYtvup!x1wv^Qxq1b4FY#xwS4^I zxawTmCDTo-Xj1<4*9mi`v>k|YB|z}TY7Eo<*|BHKJaZ-*=FJ- ziZJ`UFUw^LWXmw1(U6uSq3L~#dMHigRY`EU#_S7#PtVLQdPBMBO%aDp;CX6wIlLIc z`bZX3B}az8ZmV7GrbUxcImq!{TYry%U@sg-=Ig)R{`8U=O&oQ3=Dp(58xc{7Lj8}M z6Bb6kiyIa;T(bE8I6j?Soj!IMY&x9@^~qA!tBbobzA zz2*4-cmh!R{H;aARgj-5#>lOTk^j@0R$qEBup6*v1C~PkN@$^-~)V6 zU*hA5e23-#*YEoO`65(r8+&sArUt&ilmYOz-{vpjafpCKtIeAGQ%`(OyOfjv!`@p3 z#q}-P-U%8!SO`u85AF_u;F@5;-QBfuNN|ER4#C}B8kfcr+}*9wMjO87fA&82oc-{< z->O^11Jzx%TGm{><{ZB<#$3Kv>fV&}Uu>{VUiOAcbg#5++k3ipr%hB_E5)yZxb5To zo|cI`GmnVI*kP6DPHUZA(yDa!4-I}d8Pc~KrS>Kdr18E%kHfz=lMA3NjB)4vjK>Sk!ud-zSYi zMs(69aac&M7ZVN-L_Zcuzr`1OK7%sy{@}RMQwBYsIKf8;QhJp(;GxVNz)ZDCuUM<) zfv9+Oi$WK7fxz>@VsU07&(Zh7dpxrpPOCY#<50E1J+@l1XGUMP{ghl1_<3}Q9LFQ( zUXjV`a@DNPTvxqp-|E}Flbs!)_?13D!FWvRCtKkAnCLqKLBg|e_-5<5@fZ4wNxMNg z#|Eua=@Z^$+}0Zzi9qHh z&s&!JeF>AU(n=+XCd{Dc%dLhzfGmajf|Zn%)g^c&l|u=gh=K0PzKlNVV3rLrzfUeX z?fZR8eMK^kNCpk-yHZbEHOtvMy`fAYZxSTySs1;28!S`D`HJu>ow=fC%lM0-c-j~n z73T~{$CDVm>&3*gPS?7*YNWY;H3`%x`?RZ{(7ehN8?VH#lDfR2SyAq#mdbk8L(V+n zm+5Qg@o|KGH$AamNx4bP(o3@E!Dr_a{*13JWuhFT-X|rY$0`HKI?cFm)c0%ygZM(3 zY~yiur*9udp0?U!gt-_M@?{Bl;6Vp|aXs%%+xaTTc-0bzYL_WNBDz@pPs3K=U06(^ zY>!v{cCLC|x9TMt?d(y`4ky!3o;^f3%1;z_lb@Ze$bWT)+K|D7erutzp@03F@XWhex*-3gTeR-m7KWyF(Fz!W(VM< zt63fL;zB*!$Lqw>yl;&w=Z6qRh1c*U3&S$M(0kJ{A#J1L>mntwE0oek)MXzh| zTb)q`6`Vh2ORfU)#8(uo5^$B89!KO)ckoY_-%1B7`9Zg^^QPHt_{w&BDEioRLmOfN za{pzsTHO;*Di~~Q?Qp*%Ksktn-Et)+3_)=4JB34!`0%ZEXV<$<7p*C;QNHA*Z{ z2sc-Kq*#AAM7TyIQ<{{gwyvB03_wKDdaG$DiYC@}OmS#MkV_MFfPvx?BQh;#5Wexo zylF1%yz@iGDAnyuD38qi#dv(<<+d%g7W`@|(J2mOFsDbfFGJD=$ROi?dazOdf(`f3 zrqVM?C_9vB$T;2)lyFH_2N(kZLdEC=D9ll6T@Ow(M?Z!#Mo|-TQfJlUC#YrL3g77J z69T;xp5PrUCc`u3+yW{f!=%Y}h$!3q2o-GtL{jx6iOT)nmXR&)Ct=*fRXyGohl0F3w; z3jmUR4xBK9g~1-TXID-0o%*w9m`Z2MIKaA-izFnbh*yR?Wd!(wQ-uXgi$h*&*(dP* zT(^+D?aL&*Pt?o|yzbj=kxGC3a2{UulMAD5Nuz?Dw^?7?sj0Vu;dQE!=iZq+Jq#P% zOeYYk28Rf@+wQ&Uud1l!MKStgsUPeQ4T$iV^!wvB5ySZoFnV|9OGmRD9?rf(`boE3 zbjd?M2Or`q@(S;GQ6@B&FE$W&(9zb3Pj!8--Idw@RhV`Y+h5A4Z&tx(xGpaMMv>iA z&p5SoRr)rR#gbJA*mKOK>!Mux9Cux^XgIuA_xsBhx!R{c(Nu6{((KW|2;){$fm@01 z_M>wY`{&lPO%y4^MUo{zF21H(W!qAnhC^T=+Rqu5OurL8vp(o;q1%oEPhF1 z>`58COY!MaoqT-GM3XJXO;gDO30&Z43eT#%;?mz6Gc~B|G@oB)+V1+|;*Xzo_9XaW_Wl=2&hWvGf=5YOEZ*Ke{T9BDFO}NfaiQ``T z_l>0)7h1ys2v!a5jYuRK$^4*mq>&28X3gs@Ruu2@m$XiM-g9s)l^$+7`Cc*XnB{z? z9juD01X^Q`#bsqXz$RR_w-fWSu`jb9rrX!O=+!T$IQKjK3v<}J!ey@*G=OS3oYRLH!g;Zk#dUd+6v(BG5mGxP`u1CwSjsdptMD9Dk6k%YKRe3`YB|*_BN>aXEux zW_lv2wgTnFjyc6J4`67dyI16A#HjuFO1&oiygR$&tAxhA)pU`aJl5_Wx_*5rgFwa} zgFJT1!-565K|au!a4<_>63)lMwd8VRI~~g`YjOP*pk(u=84QJQZz$b$D4MJL>!FTY ztvd4xDbe)0gd|{#Om5SKDG4n4O(?c*o6-d6Z-9J)OAh zRpXQVdrVYni8AerCvPwXVPHf?=eHkcTRK{bSL+k$)fWOk{wTk4mkVm)k6&GQSZdW! z5H|OrXG&eB;MeCStF#AkujZ);aTKc*t)T^44}KH6?^A`W4^*zS@!cZ!99*&WY)5{j zUY|NT7H{QM()fvr>8Wxbi5{Boxpz|qezzb3MhTzBYJs3Kk~b)HH)|~SOUMD)OY-og z$FV5$OG80viL~CaeG{f4)>$gObme2v=o{ARjXtb#D!qNjMy{sA*^6Rn?NSfN@6GoC zQu2OHw)fCZz~5Bp=*@<;@%zX`K44#*4p3VD!O z(Gb>%V}tGLJLL0S4#W{(es41S1^K+Yv)qbsvGc7lTY1E42*$p{aK&b`KaEhF0q^{QVNU9?##S%1%MVMx9M6FbJ`*J7SM zB`*)M)FNa1tME|zv`i!OxgFNY%F7ep%iDQ5zI1b8xx1hW+*)T*T` zqE;95aF1o%gDyR8Z~uX>!yeTPM#=>5lrL>TyR$4iqu%KZ@w31n=|9m1{nm$hR~Xwb zN@-u0g>EXRGRHJz#D zU&2czB+20G(C4w>fW#i+gfvMdeWDWvZQ2@jPH*0I7vw@QqYriN|GtOQ+VTP!gCjnc zK|}A23QCoenuG#(;KyGpg3Ur1eIyke3Yjna9qkZ}0Ks<5{$wL%R4a9AEmb8)3)4@7`W!XU)Eam5aN6d3I=s;gxMzrctO`K?L(B z4EL=O@uf)?* z+0J@h?Xm#4<5maZK8sRWoP~~oPou6@DIG*@?~)T(3eDUKQ>=Y>55H}h4Pt=YvQgS; zf;~2CU|a|?|IC|OLG&gw`;q@FRX79WbI3B(%YM-J!uikPkq6j+f}%LmSCki<`$L{) zT!P2A@C7E95+e~mdfEylK<5)LIa**lr}_rg?48m$4~hcNux}-3k)!}f%Gaaa>{WhI z*xdgD9aR?dD}FgOKdFy-L<76_wS;DTSC3$mUFVYw+Lk=ieyqzF{N9MSnh#L3S6fwbP|(I{$!{wc23ddrHyos|W{G znMBGPN6LHA$hKfbS!xQ1Uj1x^IU>yvzFv7$*d zu|~7Mwq7wDzQgdy!I>5kVD$tR*Ph&fzPz<)QOcDwQD?jV>4ADc@5!6-DvZlO{{{3t zGTKB0ksx=3xL7xXSXO}ed%X=T9z0QgmMX7mhrdS7{3ME;X-}~C7dXbSz~fw6eM4uS zkrM*|YJg0tb``@^vp40nEmLVRO@;pS((AOu>c+mu7#f?UM>tn8tL2TRM73s2R{vx_ z0&x&U!JZY>a@_k|7TAu-`vu!mlB9}w`9}pdN zq0yGT=q({fdP+m55x18I=&vX&hXvpN&FqQNW~B=eoGt~ijcY#+SDH~`p4@H|o5}t+ zvvFi4@G2WI`XGe;z6;I74$bJ$?eLLYsbk5o_grA+d2;AgZv`xJWFWiycofysg>TrK zS@c6Fe(zhh$vRccGp#yUu8eFGRG*qQd<7*ntei;kqC&r;erSm2)_a_Ah9E?tGEP2| zI(Sedeh89J))6o{n|l@UgUB{NGgBfQ@ng2X7KgcfU{{-4dS=y6uUB?^ECq*FHf|VJ#!DlY0-xKa|fiITPZ4CUR2q!cR6h`dtg8x%tE$OgMT|aGb?Os@;! z$3cS$mimK}8T`QS#`h2D9CC+o{gr80(@m)HnKQ+vVPb*_nH4SHDGYlOkY$lZNV*)Q zL7C?wYCp90s<&TThwps)c0siUhcq5xpPiJ_q#_x~hU_G4eFG=nyfzt$(R$`QwEu9P zD1EwRRBQ^?*}3F^4~h4OWy0#fQ%vu#2Iwe1p-ZJBRTE)|VqTZtW2X|lZ;Qkp&Lh<@ z=_*FG8hK7)Lo;kOyL^rjt(3u(Lf@*^r9pfj7NhQHCp{0w7Y*lCjH?Pu1guLP2sYRs=aA$wa=|BQl7 zeciR#ED;_X(er<4n=g@S{u^WLAvGz4OYzna7Dxf^joBIS=LTcC7@+9EFxmuZj1jx@ zLjVHEm;Nj??T1gd1R`m^q1(3LUMHhX`cznMSR4^(x<$q7^8z;)YVDJDA2~L5?O>dc zlu7oE4|X4h=>ZWp%Ff$7N_{F*{MJePxx+y{R<)KTyaevX#mfgt;pj;rBE8#QbS%Zo z^HSr#nfLh^rkE#lm1_hb!Jx?w*!}w*54wBxV@e*z`mr9ibVKB7*OrTxXdZn%!;oeo zxppa!zvn0&Hy~A3+^8>|gp9??p)eY^hi>QOxUAL>u9Uvi(3$m9d=~h+34R~Ec0ebC zPUU2H)PFc+K5En#@PCr%PaN!6VH_=*{kevlM7kUYS9wKiaOdhovWR2M`M2rj(chQm z5#7^z30|b%?p7#g)1`#H7?Gy+JFTvqNb__8r~kM9YoZQUe%Y;u*!BNILuSLR0>?G7 zxyXzCW&7WH^b53)B5>h(NI!Js<^O(06ag-6%f666`A3BOKL?C`fa}iGe#Gpk|L3uP z(Kqh z?CN7|fVdQR(ibHd+FMVLMa6yu$teG>jtoSz|{h6zt-dx04iM`c6}n1=uh< z+GSpVJ*)K?L@)`F6hF@~Cvlo4Nrvrt;{OM$c_YUv6{77< z`P%yTk}YrhV{Uyw_9^_!Lk>wBG+$-WBAe^qjAA4{r?6kyW3k2`jQd^_Lj?KOemA76 zKr*zQ$gn4>WYU5Cnqq%Km%?^d%dWZE&TPmk)n^KhCR`vBVph8P%S(QBw$^-)HwczX zO@9AI6dHPNKkrpq09n~**rb0L@%jhZHcby3Y$tMTO9$I0!!t>m#~6rwQLz(o3gzRy zPudiipZ8{a@rGqHf7masw6f`wIGaaeed`!!Q*nci&)<+|K7y$bp})5Ob5(b>^_tuy zA+RDwXp1PL=>l@dibm;E2}V_asqcHn$9I(wFXa3>G;T6Ct@`4-q%H{dD0O}i+-L$8 zz-MO&&%>A<&D-cB8rsBZjKk}Tpw`u!NL#yXSiih;FAx!eC3B>kw2gs1mHN+(QhV?R z3Bb7W7};;@ZCztH+sh3=&a3^L`Qf z(!U@KL79Js&&1D(BWS;q?Q|&;z`pe!Q0%i$&2GNp!&mL_uGrI-%hS{+oL-)i+^6T0 z*;3KF!1-RIFN%p6!|9Aq5BHUAmj_5GXWO&q*E6U@df;C@I)qsxu+34uh-Tnq&idNT%OghFYHorGx4FAI%tVvec2FSF0M&dq%J=2=A zpqL?upHUMpWc_jAoB5!%eVeW32JyLGwkSa9(S3Pla0>?4U92BOQUmli?_BfB7>LEC zqT1zBbNW?eKw(=ZZA8YRAoGp-1!EEmj(7_j;6?&B60CMekq< zi?_J6i3|x5I-3ZFhO9rPY3Y<>1n0hnXL`xz9aVw~!VMo6DH*o|FUHuwOb0KKg4WHm zNGa%B*^6CC$FV5{U(;JBq-bPx**uQe@1(Y%i2#36Jao(TAC>=$gdp3Z;YDGA_o5nC zXRW|=>{EYy3( zet)0?Q9dhE7)z3=e`d4w(M!?{H${PPbHC}u#_;Bv#ED;C?UKVXm(!}Q!ic?3=h8$3 zZf1@m4mn?cZHIQhm@oRm5A9CxKq$u`&)t-6tGiS>(pB!>;mlqMuDUGA>hxEV!(R9d zmoo5fS-P{BNYmA;=^M|j3hg`DL|)y)Ke}I`f$Zmh6ZRK0I+%0Auws2YFGyyKAi6Er zal*^|a{FJYCy+uKA_TK>K6!dM!Wkq*v2sP3BmNYQM_U)le7@y*kQxbHuaFBLVI60ubHRW-36bT+Pz=C9dzI#No@&@=;Xz1+u zwXKlqnI35n#T8pX2;&`Lr$*ZvGyq%PRwyiYpuGZ;w2yGn4-cs%p=vML-r14jbif`q zzblh=a)8dy4?uzauYU~3XhU9#y9ma9qX1+hrxRZoUqXN{LfXnaAB?B1CL2SxtpyKI zn*;?sn&dMc^F1LvQ!;FeG0|S8Z3bLpiVR(xPZdeWTh(C{tXIpGc6=Auk`}FE5C-5^ z91`w{$e2IOY>r+t#QE}gtM?Y+^jXxyen}B^l=f~+W=y#323>jm&# zK2wGU(+-ym+6>+oYk;~6SO((-t__XLcC(HR;jstcV6A@p1*u;SIr4O2_I2G*) zLd*p9L`&DzYr0>nBQV8(=+;*Xx`7j4R7npkfMFeW&8n(t?Jr_~qth)~47D@n zf>Kk(zQN-+K~@P|dPvVold^wmy_LP5nM1?_h}El%V7dzr{NkQ_1@L5yj|tv=N&d=D zXji+req8}B4raK7Gg*W9gB~j6Z4v#7fB6$Fsh6jfpI9>7UTnwDSQVHqYQ84L1T2cI zOxLl{t?ydu!r)BZ{HtJP#@Qmj{}5V&zAR9QI3H2=^sGd}xMp+J?>eQ*##dzhW>bL_ zer2K(yswLTW?TwBfZMPSnCfi@A_pGItuB)ObwVbDEmmxuoA%7Yf`imfGx>ovcvCoY9vzvma#v=!R$@t0{2GTw;}4HUrBmX*l;pvhi=>sd zn0M#LIl+vgmfW*Zv#;Y3KW!O3Dv|5kvxR3FE2>P%REgsDv#({5!u2%O4Z*#YSV6lH z3sN(GWb)v!b?7jDg#$utF4zRUh%pzlrPA`8f1FH``SbSuk44Kb5rYDhK1M68(vwnF zj{YK;b?=lPS=TYphG%(aa4=gyuoLg*i_v63rr+JafAd|XzUek=rVkxWJU_B}p;aQ$@o6UmJ^J47Z>yPBNz}R&}ue7n73KbOjLK*R5-}x2t5#WJLqJ655R`SBm0~&Ij|Sbkc=PZ&dvQtMz(MZ};yDEb|a}79%C_kCA74{~>RF>^SkfkWxVKlPV zTS3t^juCVxi>pgts+QX_W$zR-@|Un(&NUySRPWSy7@?Hmx_Dz zMw^Dnn%sJ+EHOo`!nwz_|Bwmm0DUBl%KQ2xaoi;vnFa%(X@auV-XhFAt5Io`NyOk1 zh)H@I-s}d*?vNRGGDAc$5gA=U%1+~O|={kE1lbO(==4+F(7zbT<2_tmDBhg#=5q9tdH*c^r zrs^F}ShM#R3>+?2+8KSBsXT7od{x2-L|!RDw7z7EI-C`;Cy`Asp~od5k}Q8s|U7K39J2&>=z@iI~$M2a@$=onXFhjVaI_p^1!Rc-su zOJVEyTKE??Q)-$QhuXbO)fW+rFJ$06XTl|v=}Ov(tO+I6wx%+bn(Poh2*> zyMS(azbwL`{t z1K6_2XB|djT+xnrf6&sc+lP8plIgF-?l+ne7Ra|Pu%1T=u2h7Z$Yl@&8T1h|MgjMF zJ^~3@o}k1X?F%nxw!0IOImmVeMm#BJ#g_i;3KhP=(%kMtN2IZ#l5;(;1TrYT?G#u| z1i;jD0+FLct_R@5!Qx5tPgTovp0qx&cC2}M(_MM_+zE1BQ3FW|SYST0>uM@ov2`ac zeIZXLp1QKosad$BdBLyt8`?|OsL`5L2Ht^B%sAGi?j2&P*H&0YcKAbImAC_ZsS`%C z@3yWpen?gDoi*vbyF5D--H%sHN4l$i3RE)a zwsI=k^oD$zr8ag^cQZp}QG3X2G|2JP)4O&xSmRbOx6URrG({l)7VvN@Ux+HT$2*|t zGu$-Ld9F=<)I)&t_3^iOkeKraFls`|&-j%@6!#T#`F;bVP{L284!rHg<7fo%%HBe% zw8sS_eoGyV38LUciy+^_u|w~97RC|VGD*>f!y_!Sy7XGG-a~mahF*z*hgtEjQ@e9# z$sxSBWN8KRQ&F}X-)$=~+Nfz1mhXi$NOP#c-_=HFcE%Ei%aSx3-;%*e;6GW{IZa}C zV^2o&?oy?i)+piw(oWzZ04h@S^6|~XX)ZC{JgcmwSL}d30Jgj$oBK+%`C%=7e1rpN z|JlIJVp~4LGDbr=47bDfTM(zoApy#mhZ0U?cAkl&BDsN z1Kcn|x@DC`td@B}#;$&2-_0dA`VC$qrf-8DtODDrck27oiSe0$oZ?T2$o+BvtrmIV zsfGqk)WWx#;%hjvgddA2tIj$vV}cVTScWXLC!4%E*8I2 z>_{fWWH%6%kGl>K$`Olq)o#q&-?nL_gK;0UpYJ^V<{;ZFOzM8wK2;8bI;KJ=lPqsv z0Y2XQ2iEOv?D0c#vt^`uLwN%ub|QQl*85<3YcbuIVfl!PqJIop4sdo#=981KeSO zzS3pw(lE}ryN6Q}nK7{K|^fb9c` zSMUm86v|RI);|BA_X!E>@p^hu;R<^Dfd4%Pbog}bM^QS;h-?Pb;kEs zAEJWa2EwcjkxCtaKl@&wh5yn%iaebKg(?-Z7cCI_-|ugsaoo`Q6wKzX#>w)ZY(5^HW#7ZZW)Z7=2?U5|V960ZXis zMP8<+wc(n|w1dpkGAwP@1`L3b)XSc{l@oinLXjVpi%fuBCEu{)Rlm+d$5d}US4Mfz zN!cW@tv@g8VmZcI-e((5;pIsLzDn+UlX}}5k_eai**9m8@U z{~l!M3xum@J*?pGOS8B4i&4Qotnj{9s3RItG|)#?l{fmD z2YiQ>c4RtbpU#dl`6FzxPSE5e$4nP=K!N)TWm~g2;1+S@mTJ-ch;Sk0hnx{1a}xOfNhrLHqF`hpfRg&m&5@# zmW~oH z@XG>2J%9G6twchJrkIxjwjF?H23;wQFe7$4e4EfFg+K4BwQoJs*Q7_&N*?WZY^k?}8t+LpQD076o-;5BM^ z8g`_%fnMYg z18{t^QC}A;o~w;WxuFWh-@ui!l!Ld4E++L9R&7I$%saH=A zIH$EeEi6vvLF6_u#CgFTsFrhOZzHI5 z_e864iTAs^ULkTuouR8Qy*YT&=`7aMobJImo<_6pCtWgpthz?s#zDS;s7^9okGw^3 ze|zckFpqfway!Kl?Rv!$Vu65&3ca&AW4{=uKVxq|rPbsaf|AR%_Vzoy8sS$Ndroz-UHAI_v(DQu zW|S89qPEGJI4vMm@Qb_)y8EMXFJY~SS5E0#$fgdja>oDjn&<9qdL3GTlQD8(kD-4! z;7p5hauH)pX(+x#rZSP%2*Gvihjnwsya87_naaDwO1o;?~ zbhEJZ%jY({DX7u0b5D}v??WRX``HGfH5Of~M^>&#zr>n!20>pd)F*>iq4Hx(+i%BO znt&pXxdG3D3hoWxkX1K?Pnm*UA}gD$xP5a-5VF<*D7p|D(Rj5}(6zbyfJiKMiFS2f zPXh>^=H}7HGT1@S$2rgJxM!v56shXpqJr`ne*HMp6to~T@=D~%Dv8)Tr`@oOQ1YQW zQXTTJpi=idqw5UUG8a;dKVN@=CX!M|lzK5!- z!=LtXcJ={>sVS(2;A0U-Vev0cqY>V3ENgFEacWIsQA#}4c)b#YkoJiuDhnNh=4pTE zKgJOODP7>PPn$Pki*k%fJc#T%+GiIryJHWTzGjyD^H*#acztVl6BYH3#-_g2qUm;{ zSOWt;OL#Z(XUbf?orzp24R3!c#B<2IlUL#6oUk6jvRe+Jo7Vz#k*voPp%E!EQ#_cg zr{^KJbl{1G`0N(q^5aF`z=LpC{KLk0P_jNPB}lmM80VOb`_}C}GRK{PC3O*Hy-cE%>m<&OgkKx8kM5)Vx*r{VlyaG!9jR+41PBydt za+kkP9JAUunEuUFNOpIE%kw%?j5wRpBI)qXfn(&WWUShQHw)bN~f7j9^Ghh z;%W}BUn>gNd$Z!|z3mwDP#LIlnownurqYzDN6qX+M+n0;n84XYmLD-ZC-T*RFIzU? z<_Pl#3ngCOA}P@B`@);wzM^A)ud$=&2E>qGN6u>ZJ0bdK7p&^x`X&i3A| zmh8mFLy<>pR+^N6;Zxzj<|;8a$^&l_F{+wJ{`CF~grwnmjv%}$mV)dGGlkU)n3Y^~ z&9*nNqy4G!eqLD$t9RgKx8&`dII>TA%A$0Tx3fd6_bx}Nsy4ptE(gdufvMNh*>2gW z0P~8*Vilf5x{7l8%qO0sP?=rE78KUio?JshAPf~dUIal{Cx23j44r1Ed?qSSo! z+lMlaRAA*_w!Ts%i3m)?uuaNLR41)0s0B&rR{Ruer)=r+HRu26=b&ZNN+$EV5G zf`;S@fI#qAiXvS+z%F-MLBwzD)G=eFWdoZfX%+sJ&W-zvh@c%vwLkP8Q0&<28C~PA zf9$uwQ_VajRf121d#RXI!v0c?z!yn!S)_8rv?a{_UulxE?iWw)omZ8nAQDL$znl6} z8?)UrRe;e~*lb01p7*Dq!(^0ey^o}y%N+0Py@QE3E$uch?yxaATtUT)`gLww8d*ya zG`jM+_$a?fxm){JH=z2*v>ReMyw{x~sxZ}L=QEtGs-@*>7%a}%HlFHM{{6FqBJZ6) z)~@Y|^_!xm{IgCyvg_O+6YnT0FY##u!N0s4YjE$!;LD)o|FCj=Plj7O%Cw;)a0lQ2JidR>i^LD8n0mkd z=Yw#2m@eGmQDZmHvHKs#!G=Fh#_A#SzhCtK=gpgtY+LjcfVMgV5Tncb0L@mTTK|L? z=bAa(#t$|lDm&uN?B%%u@G4}wLmrz*#`F){^zFA#yIFoF7fGNP)pNwnkO80y?2o=QcJZ~Sh@DK5t}Nj@}S+qp+O zXyXOG-Y#AuYN+_X-m}z)XZ^)iwXaA$NWf&mn=(J*tY0DRIjU%KeUi{+Qx?Efpq44$k&T+MGjy8OQZZ=xxueu z^|kW^6tc>qOm=;|NOX4S%5wh4(b0UkVWNFp)Oq**OT;$Z7r>j@4`}urexoMZC?QBO zI%502r^?Ih4=Ynv&aiH`xVYo;+ZKZ{~Ag+m9I?Dw~HKd zK2J1@YZp89nLZIV7M-cK-MsgFSm*xw+oQV3E`C~USvz%zNZ$`%1`w2HHH;tAkZdzw z^X0@eG~yYa-zl8@C5%#tJMvhL5OmQITxh80wA5L3r1edPxB2~g_n}_`K?Y^#uco0I z1P(;OA8;BH;A^vlZ}66#Um|*Bifa}}5NsuC$iGIDs1x2`T>6k=+5vn|>A~K{mA_)s zGEV0*w9kGH=}bXSlZeyx`3OigVNeA}Tf*-W>(L=Z^rHEoXf_>AuL< zE|i);Lg&bWJH6ry1>9pV#`uN>gNBx_MVOP!Tin?|@C{O#$3eVW>JQMcbZ=;i(^2(+ zWVdGNddC^m+i7%KLpmp&FSo$**_-jqoFBWt(6nzefBLXGrcYNHMe=@tEM7fq86FY5 zJa}rO0=(MrXi~r!Dn47&Yg~cl+m#<~_b3$(>8CYHk6M??w$5BX(_rdB`;TW{XCux% zrnPs+7cRSla2Yi9y?y(A4rhg5ZmMXz>B%)Q?-cMja?*ua=Q4ie!K~t$#P6hVO=`m< zaYJy*UDvX!SnS<%MT5l>l|jd0KB{8ImnXKoFmc%s8s~A;_W&Up%3jaig6HhEdb>lm zW>9!6mt03SWm2QZrz3Hf;>MSnon>liq7*vTUz8H|NVXfHEDv70PJf*+>2U+}kky)l zy3R+JAdxbrw-L+<(Z`VF--Ve*ygtItNsa*hMXBwHaH6!9?T+i-+~ci=R!A~9S?|5` z&M42m@8dj0AmKCTkD3yqLx2No=5K|5mIqeiYIKLonv=mI2Z?8a(@k(Yg$ewXApe4u zvG~y}RB+t<#`?|8=0<+O81r|iGs%u-F&P|f*BtEJ#lbS;3RV!A@A`QNGwG` zY4{-4ESAv;UJBXGGAAGx0l@Q}4j^QEmt)bXLu>>o`FfL<^hK9bwDkF=4(Fy&NEA%S}1*6 zkKT|8ZZ+JP-k4>Pr$I&pvi#yjIXWz_2*r*-!fFBDnJ^hrSNEH{+HUt z?Y!;~8+>t`M)NXO9aG`eI_T20K9cB<*R%s^Tml;HYuc zm&JW$LHXZao=}d=E^?G9b?C8NaZu4LEty~2x=%}mL=xY%jk9kz+CZTp_LsLoSLG#| zbuER(TjR+}S|XQCDhaR6IL)~AuH8BPlnp5SN|K338f*Yz56}Tw%bAjPQQXUoT};Yw zrHs_s6z!sWWg6w(MZvpZmB0H{Opvw}^okUuYU?<{6 z{Xawgy^~M24%^G1c83OXU{l(^y8witnBOUJF%O`N3$$ds*|J7GUXqSca2lyQU+>*m z=hs%c@0_!gUU)pH_!+5MTtB8o88n9F6qCfY07)tjTS)&f-1hOm=1KuYNbG&!I1T1; z{@esiI3Fz2OrQg#rfm1>VU^Lf^Lmwim)Tn`k3U-|T8T0fZ8#mE))Vtw@!suG8ihSm zOR*DMti$0T{_LQyYqS8%yX{$#5d&1lud{n=ufK|-Hn3Rmx97oAc+Y`*NeaWtKZPY9 zRh7tlHv_OR5JyR_(u&SG7t5%E&oG{5BpmyR^hs-3%Pc-kOoz|#$43OvJZ2lHchm(= zwRG^e<;C&P`Wc|uSH=+ACD04@VzMA1j>>Kie^K{LF};krDAKFDLsn?41Qzz;x+QWmI&p z8kHY_Sjg_Ti!uib9Urn9TGxxWrFwngnO1GBknhw@XQQqk99uT4LC>wI=!Bo>8hJXt z6Aq$|gb&xNRo27^fpR4L@lk3*&@&Axma*BCLkzZ&85NXC?EkcT7}F~Lg45MV-fJWE z;}US(E`SK|s0VKt&9+((ggQ)YB&xxPwU6P(V$}>50T8dv>O!02}!P$i7nOY6rBOEw0cOpSYd>*$yaVVE6pL`aKW&nB)_#@K@s2};~7hwB@M)`cvxKtm8L+8|K zgv0*Zc16M(xG|E0ND*5Q*1a0uXSck>=viU$`-=>nxFEF*H9`{0DE8Xkwz}gIUZ!Pw zFE}NG*^%>B%s|m2rNRZ)WKw(^g(G5yiz-Yc_7Akyo7;&}U>7z#4v4U>D0Fn_niI0}wMXL!EE7 zE=--vcVNyqKmo-;IOGz8@q3;ke^I}uD%4QY;<7950<3yjKcq}KbTuHHE846)#- z<2Gh?H&`C7+-9uYX{yljDUYY)5x{?O0zBzy*s5Bfqwpro2t`iO;$Woeo?yI@Jkll4 ztQ6jWlnMcbvCFg6f3?m8zA_=HZRwOhpdvu{vc5VsjRB<3mx3OAsw3a($B)*<6-zxb zpp}A(BQ|HVvN=b!E6#~H{yM4d4j+5D?7mIQ>N%XmKN!=;BA-;1Lg5`IoC0lT(?%26 zo2zxNWDB{F{pPHT`~R``R#9~>&DvjtA;Xh^&bIZUpHpkpNq`{&2`nL{~!yZ|0^txgKSUm-Z~!Nlv+x?SO!GAJ_U!XClMOu)I< zU0k!&oN#DXSeZrNuu0q zs(~+|4R~}+0)_fZ7f(E!rDa+5S@0t9Dab$tjtf*3TMwL zD6S?WJlN0S?Vt2kI20g`>B*V&yJ_O6I#*q+0g-T<7CT5guvo@`M4;D67Cg5h-m|&F zMgayW94+g7s&-eWrR4i+af{klc+dKe8skoHy1tE>@$-S1$bnJ_oS?B>BNq*Srz~tO z*ckHbRY9=Uz~+Udj323^*b-JF1R=2=aUpMaw5ASa3&=~zELn1SuXDk?V^JHwEBkHx z8^8AMHz#6hlO_gJy6e5~sqpdjdBCk=Pp8YlH$RnN6fTn?yHFTauFHcQRG%_Ue8qP! zN4~nb3Y67Iqp6HN__xkb<)0pdFm44@wH4t%Ugws=U7@^q6G zzyprpmc;%O5*k8U^Hiy*)De^Yv>rZ!2plO&VSEg&E+NY~9iMX+BL4fBbcHeE-i4u% zP)BildWkj~sbd_|A^ZSbeT{~xP2-9+_djLtF8;RA3tmf0n|u?r5%cbuF_x2}lkw~a zh``Od2lVD(8jx9NAk3VL8vaSkRw*f|>kBf38y@goEjbm6?s4$FVT~B$i26$pzl?VF z_ii>6h>0qtN>zmIx;tOuV0v`&)(|)kpDJeD@cs4T5rD@oWB@*u2_daR*;m=8XWBeE z{h!#fRf6xoGgX08_8}T5@jFP#!H#^0XAB6ZHPNG#VSI(1Ukj+dlB#rqcA@|HheqE!Upq?PT62f^@^i+K1F#ocC-ydcn ziMI48@hvxnK>T8-|M0zsfx=+bQN#>n_s7@!Ir-=Zd|gpN9;G# z@*r}2B$ClG937FA;sg1Ce6)fCs`@z3YwRuk>+wCFD~R@(?sk9kC!R-Wgj*NV-%fkz zC!%zGwRoYZ#KFOQL=*i-RJMwK=5gDZW{+@gENFv;_lEL6YCU3XFEOLy2H^*Te?S!?zj0cc{MHfbu@J%xVw^p^7$b7< z|9HAuN-Km+rD?@0U|wO@B@7~1qkznjRzf?yPyo9MXahhlvgP51V;@5l% zd%a1JXmR9}&!&5j!dIcmHd>pZ*yVDGuaD&#JF>*Dvuhs3ha;5Bw^+(8x3dS4=a7WZ ze$5|j+!lII<-}C%Z|~>m_6rQR1JJu@nwSVGuwxy;0v%pawu2oMp)=*poA(PXPHDw9 z@He83mla;d!a!tE2$haGn*}pO{%)Ii430wyt}T-f{xyTl2N`wr8`u0W9EaPG0+Mw+ zh@1-kRo}uOkjj*z5`xrmj3-a|4`areaW)wo`p)7Fm3q70`G8m^_o|TanBH|O{z9(g zd)V!E_05`gbKp>qGrFpWG%O838F}Zg?phsVkHaJ z*5JoL9^B3wC$a`n*D>C2%uJ4j-#N2a^B(r~P~dt0gN(3&FLGF~xu5s_zDNlWgMT*j ziJB>OK7z-k0Tr~LUgcSJoTgIEQUb?|@kODdplq0AB|Hwa5rkGR#@ z#$<}iYK=6-P7apOcJ;wq5%YUotQ_`D{iNcp^y0Q|)ahi_xM(CeP8ZY#vW2kr^$yWK zyPoEp2DrR2BE3>XU>Fae$J8iOZ@)8PRJYF64(Bt~*X*}&g85k` z8LVSD>xmOc`Lo{Lp8%LO>9|S@=8%gLkmbZHeT56*=Sp6-4stq4xb|?uwc54{7WBGM z6VsAjtIT2Z&=;$=RK&|()jI+%jnIFHyvlPW>P2c%&Feg=`-7))zP_&wpVSC;PX~STk!u7@$rGc=*zyI zv_JaMhITtYlg9Q{xNPQ=ri36Hf8^Xri1td#X}h~M5%v) zp!AV%^verBcdvEAAvVm*mhEpB_4A|^50q$v-!3+ zxeUbrEWZ8lR`n}F%^dm1CIQ8zMihfdO(6eh9~J_&v&5tRe=q=%@-y1P8As)qt-|+z zOvPX1W|CcLLP4JjJp?6dTEB9Ct^qf+|6@0?1cPkH_stQ7KicEIgWAbxnJE6)#6*T~ zyA`|QL&Cr1OEmme4gJ3ZyxG_PHxZ3wgFm@;r+Hfpq8A1Bt;h>nQB3)g~>%>oD zjswbDorlWRzDyo*CHwr#)=9#FgI3d)S)(7O`_{uJw~g(k7jm#`DCL@e5j@DlYD0hHQ>*H|<%Ro6v^gpB|*Jao}-aKSd#x76GN$Q8eX*0j-DqVtN9U%+7cbxb6xP8?{n?hI-8F3Ypt5* zcUM(`S99T1P{O0(*8I#)=B5`b*&<-iGj`MSv7J-VTdR{pWI9X6&%6HmeaiW-It8h< z8i&?vcaJhQg7f&Vcby>x{SlLJ&!+K?c${B@y?D6u{3oky-+SIeAFBpW8F&4?i;y!x zQ#&v7)Ta2v{Aq)IR3%P!xnV?Me#81_h|LIymtgmZW!H1YHlNVZw$m5mSizp;CwN}zPp9+`=28tXrP*qrQ!_&C8NeO zMOrw%{V1jN$`7acv@4xKzfSimC6FvAJ5;u*IyHiE2}sSBJ>#L;n$X!Gf-Qq9 zQ|}ZXN#K*_neP;l6X?jsn*}>9IwTz0X=aU_2~JX-0Go zCGWO;#AgoL=I?G*Yb;M_45OT!QX*uq$3t!sp+6$3j_H-CTzq$6D<3EK$=n{h%9<}v zTs!*?Sm!7Mz!gl!Dp6uFKb$G-tER26t?SL)L{44)iVISz`x!>D4#NJ%nuIkv*iJ+i zn_g_1AKM~!qDm{0YQxh_GU>HimBi(qggRb~i{rTt(cOxi4V#;0HOzY&1QW}($a@Rh zNNE!s#AIjC5KiO3iw|!MAv=X9ZEJ|fX7z#}<~Uj74V}$BL`0e^EX8oGQf)d}J_%%c z+4$3`SsTj>ihNY7FuCNpOCo340W|LJKJiN38Y=CH%vr8cAw}YlFgE-qg1Xw{EVb@s z(@mSTJP&tZoVD*%zT6oz-g;3{8+Df@D0MO@|61k*Zkabc(cW6HIvRj5ip$W!w$)}C z#!52ZB-9bxAo#jwnd~y<3coM7*3_NZ{^3Z!>Fw1#{A9A)a9ed>eyK}3o9rz^E_Si`4~oZ9J3NA z@;TG9BzsNA)!n>so^76%(<)|)Rp8PRURa+vi#3UXtxVv-J1X7j$b5Qx&SSSeMR_+5 zsn?IF@MG_Zbcv5)uUp5>glvolFw-10)74_-6&7VH%vUI5N}wsK1ZRKC=-klIXY=QqEC+b4JiKK)X=f5gxp#YRrf*oWr z;j!g;t`(jXJ@{n|gA?g-e!6mr6ZN=<`TV8BmwRKd{=+mBf(qY3+iFw>)ef0KM&bsq zQP05bVMzr5DYmDqVrzdWTA-FUcWP-u2>9W$Cl?F7TQV}_%=mfYd&)Q&?EAuFvi(vg z+JdJ%*@6HXG2Q2yKAFtvd`a_ZxWk=d$IPcOZviXX%jOxDLMcj9M(vp1LV2#6Q$af! z0d*$nl0(RP>5PFIw^wV{6cK2didLC>p;MK9%X7daPQU)8{5p>D7u>VdCYuzBsq!^cXS|_zk?n(%iK58 z-IylLzU%f^v0;Y2C-Wz0pTREUi@kNon_GqcGQEbc;?05m{mRcbDDg=pL7$VW!JweG zvEWG2;lyeo``6v%0zeg5Xa%2bWTcb%}OQVhSyT0rU z+O*wLmx(CY5@!%t>kZJ|+D#l@nO0j2qzP6?$Yrsikjt99IP5O5FM2$Oz8o}8rpvT! zm($-*I4OY$qVPL=vktA}8yV1oamasqRe1RP-l}Z##_FMk-`*b_Ki*v_h`8*8mOpU$ zozExGR@t1GiJ)b^PM8o@@~qX(4>@un)E@GMQE7T*d*u#>kIL6S3^4Q+R(M*j{RPD%$J!yK*16fO%I@aT2``FC9+2Ym_kl?i4D-`|2a1nk)Ai$r z?wP~+9}#VLU-lKa^FA@xU^te}d{MQ0718wJ>f$@82Xyhj5+o)pa}Lj9Jxa#nxyd-I ztDM(0JHwu-LTmk4-I8kN?1vd-zUU6b1H2kZ#TD%y2$<&b822Obz*-&f84Lc9fAmp~ z5Dfwk+7(9NsdhYut{2K@exe%H`Z&#BU|0@)`6W_9hNTR%=~Pw4Vz>vvVrO6+muKqC zs0=^hjFDo{Y0IN~_tUP1D!a-DcTVpSvO?(uO?P?R!sEsY>FK_(f;1Wn)$n5QrGuCG zou;|>qkRoNw?}}R0q}nG*LG_1+pp>gAJvMi1x%!~6qlmB2l);|j3)?5eFhcOp;eTE z8%`*wXEl|z={f76ubD7AluB${`gI9&*2`uUq`N9x8%9g;(+cE@;go9C%{6M=0(?o0 z!e}HAMpam!l+}QGbO|SfC8h$D^M$~yB7?=<*nt5!->|DEc_(%}eSI!3odnwxA0Kdd z6AI%r(ej)QNSP9JI*Jp5GK(5?Rg1}9_tnzbM~G4CKD8`^=fcn4(Zk9jl=svCsFO6v z`K(ZTr;M%N!SkjZd5sNY8dm@}s;N#fUa6uv9(93vbDyrb=~`i0k8efgm1jNAH>JJw zv|246rqz!9jgjXRsYG0T4VsFgB14doR~Ws44iRHVQ|o-T@74uxPvT{toTUn$B2xLB zRHVF%b>M4t>-Fs1TsKC)t-gq5mH|VXIg=qC2l)r|cGGh#;Fb@uzkLxf`|2kx@{*|$ zMkSw?rS%WC2n*g}%D_C8s;)%2Qn3*Q4v)~MEFAv1ALd4w*$-YWdN^-4GRkUf2ky`{ z7b%Ig>4OG!R%aE_PcS-i6qKfwdsHfflxr!w(%6?T!fxuRs%6Be_R0X|i&A{pMW!%fIaRh)f%*y;o>6Y-mspp&hV~<9? zfHATQ*j96>h3P)ZVM%FHp-Az8ap*UMRux4UnrDq`!i`{QD)@aE`gf(4w%lTsC$&gO!tl|mjN9?AX;#!Rr_ z3HiMc{8XS4^?Xz8keiheYJWpVQe)^{sDrz*$TU&5(Hicg-a$57-_ik(LQM;ZGJMkZ zd-{5`LyZ@Nf~og)Qnp=UgCLI14z<2y!X*{V7^@;lmD@hru8t5%2skU?cmL7Q_2;`$ zr(HcLc(;pZ}ev%HAUKdi>P_Rj0#>3&`5mrRx z7A@a@G+B0Qc{X|8fJg_cZinC~_7%QY-ICP!+ZP@$QK(N)(Cbn;%+Ud&d9K;Ii4>f< zwb25lk`g@PDC%3m)^%pyp{r_yT5iIaOzF(q>C2)~xJPhQI90$J_x?isJXEI=u{#QS zvHDzj5;ikAgfjWMP&RhtdNhbuO~%>g`!ha#CF10E$0N)L`d1QWsq%99i$=ng(!@gk zC~zZsaRqB;;R;7x#k_?a&!!`NdK7dBC}r=t=JZ_IC? z%7|@PdrgErJ?#-oV9}Dn>a>jZ@>T=i*D2Wj3>eHqkz(A1l00tw`E$Kjtc=|3oi2=` zoQnD$f5)cRd!?v~($Xvpb-ml;9l=65y4r%t4FC#7l;6&Nd336p`_Cpa2G7Ssbzi(8tG5mn!V2k8`tT*vpcuwf< zj=hF&^<-1vZ#YYo+z&Y`x~PmiD)B`A;lhQkgaOOK_Z;23&!1ad*q?T6zOy{eB!PfQ z#o(pO*=bogu$8q1+q3szo3PzhG2K)`Plx!Kw3{%b|cAIs+pZIlZjxaXIkr5^w zJc-4F14_7AM#m-l7vtUZ7GvU~cO;A5)}p&w74ryp8t0GdPjjD*o<0c6X7l-!fbngFbI1>#4z&C5uB&Lti#GVt!DD_B zjlz9-`I@(2SXMS)%e|K^5dlyl;w3oK7i5@4IIFZKciU6$~tm^org;rZZz@w z7IFRbV#{I|@;4M>00yQR)pH103fW9A$$_wZfKEkw)_6$bWg<5lG*OvWcvj`S;PPI7 zya%mC^$S{rAj1fpy1;^n%bEzsmJFDCQd z8|Iu8v44yLh}FGtO$$2WhnKG33Q-+UwE#!RH{%L~g_E_)_^p?8zho8!603=p#AidLX5% z6i3(8#B76u!w22b%Y^+SC?P-3p0#8X>ZkET>6Z$g0fQAuw4x3yzT%)zwB(5=1rW&q zf!l+Su{ipXBPAoNOkP-V2)m~mI@yAPN;Uo(B6qVze)iEVseTZ1EgE4mS>59(ikK+i z_}%nO6FMb#P5P#z(FYtc#+x~VObE|ld-QC%_#N_9M3FwHDuvv;j^$ro7XdP&yk$Nj zu3nyuM8JWCh>tm^C2KC{sB1=HfTQ-jaM)kgpy%C#3um}a{|O4AqeS5nPVq6njAfs#~!yzRrYh=ty6j1 z)qO_Y5A~Akdm#mvWBu?tqYZAk_7PPV_Y4moCu2`oA-B6)@O5|BC+eYU=+x^rKJ60> zw$GNl7lZNpv>_f+tw%`Kw0vmw-Obw+rP`)ReE7+21DjGEawL>a-i5bG{;vRu2Tc4Z zGAa|@Z5_=PJ+~cc0@>WVFLiigF)8(!#2zxV;${K+pPchGE`H1|7@{mLC8fHLg6JVF z(sMPLG`ub5M4j5<>~Z=cZ1uIP^!?BBy=5-jsKu$^QNS+*Nx;Rr_;arL2H(_=Z}j4B zQI~Kv%pkCZ$!&5g&!&4Ksa4-K9A#v{sWmT&cpR=Qtz+bElE<_kn#x&gy!MV;tR5O` z5sWlCI^Y}lfscL5*uA5%iYEF9n~^Sw;~GzZ;MWTL&&;0|V|UG$2kHMYqfnseL8CA` z2bEak)CyMy27pdfzSkX7rlXqZxt_wxQ*Lehy1(Xv3|SgxgB?u0)tU0rw<8TMmI2F* zD158=64!;# z{>5+M4L`O#fNJpj1Be{}1&P8h81x|iKm!dRKr^q-!CLqyW&Q6O2tRB}P=I8h)feG^ z7Dc=bjQ|4t`h#`!aR2Nn9R$8%pMi7y12q2QMsXl#+oR*U`&s?j)BjzQki^6Nh_c8L zKN`bZBX;ijhu~f1d=v9r1>U(n{DJ5TgT!S=_P192SK)>)D2mmm|3|by2>i3}2ujEw zjH2&gsX$AF2mMFCWZ7>WWB+f*AR+#*DE>>6{(mA0B4%okX7*Wun@(l|%6WltuM_DB z20`$jGgr8`Z=qJ9$_0Hk{qgSiW18cBFV=i+sWWfW`IS;@O5V>;O(q3$6EM~zy$CfZ zbc7{{m>X&xql@ttYdS<%axZ$dQn@gj`h8YCy|xWva#g=q;T>MN3$WTGa262szBHG})8# zrQ!m42+#;QtQHvcjF>8W&h!`T&Zd6P)@HzfkjARLGMg?Z~0wiMr*-LL}jYS+`{JB=YD(R9{v!V zH$zIq^Jc*1mda>V(H5FbE5B!)H=7#Nvyv%$*5N7m_{{zTcT2>k_ZU)I-nA- z{>Xrf72Gvx;mvMdDECUq3tXdCp-n^r4C;RRdiu1^X_K&9s+u-xF;kObG@L&C`=1Xs zP6Sra#taI46qKsaX{CU=+>0H!A!HXjd)?1g{C=)KEMH2Jk7u=1Gu~`kuG8o@qDaIW zW(;Crh71Iq0KR%g#AL`Xkms|`lN}I0wtAq>02g_|lKzmext+vi7f~>LFFV`zzwcp( zGgw378MQAPMnaFhI*peR9}I5Q_EL}M-4+z1NP2e~-nFm{Iwe*}hfS{z&zPoNTBufT zQAt!)G+NF{dau{@t4EPc`z|5z6Iu8yQ(?_6t>z2I#9_o#YYOgkIbq*5T#Xxd+1Ma%F0g=$BbWYGF`npo`V7VGbX50{%&ja= z*~uRTOSyzF97Eim4m=ZFY)0s>y>Nm^zxY{^1EGlGVQfM79iI>mS zp6tW^hJH8TMFxlUfPE)+XBfae4sp!-Nazn-ZuY3W6eLyom2utEA3;3GVjACG;@jI2 zR1FS4d2>8JUQam140@S@es73kEqTGybTWf; zy*6U01Dr>o=FnxF{uPghj?-p^&5$`I@os;e7wTC5e4}eVwX(?M(1IRk0Aa}k_$>CK zW6ZJ5Z@2YRaL|l*#r2}SyM^7;8J>%f`>|5$a}tAW5xb-_-X_s0J}eqV3@)ejPzS{8 zP`8MLjN1XHR^dKBc$Uj^{Y*N%i+tH&4LHzNST2F4zz){xS6$9VXS?ZwXOqBAZ#$FP z#P1(Ab|3CKl3JYE7Hw^Ovu)&$u>&>;!jFOb8z{qx>=Ed*XEpmGgeyL_m0j!@46dDD zJ3JRxd;ny!B#X6c79)1A7i}G)kNE4reK`jtI(4GX=FY`uO1aE9cr1q0Xfw_wq}$hn zc4YtKiK3IJ1DAs}x|DG*yxpxvu=#yi;IH@O_xRoIn0JCU79*a_PDxm6HnO6p38$B! zEBQsN&_S!6Cs~&Tg`XsHGLJ)r=lw;W0{M-A1Rl=vnEcKKZi7w zkM|+cRWU&N-|E*y+3(4wQFU71i|8D(5`Doha@g^J$hR!s+DkuoC7C&XS$8_>@^>lA ztMYGfbl;P@x|r;A8Fpa2fm0zw}qZ5!@k~ZGRj0^1ygx;cVzTf z?h;&NUo~2c%J+Edt?^Azy*I!4*!gbK#i4Lm4Zg!=X;&Z^Ez5H8%N($!yIP&3JkS0x z*OO?oS~w=0GV|oy<&t3KEsmKp|8av1lfXHDfL4+gL5ZSD)h#r@g$hnK>$e`FM*~h6 z>AJ`AcZa)&h3IdI3Vi$pXW9Co3bSd2VQd=_=xe*LZp^W#t2QyL1hF{VYxD~CnhnPW zaa{%nyf!IbGCRvohs4R2c`KEj_hT|G9yeZtZj%1r?}467MOlj0t9LM*g6cupbqIkI(#WkQp{X;H#IAAu+6l}ds@?*-yTnYie%l(_RTv{ zEm5Um?1jE@I_1Z16%miB4~bMVIb&21F!98+A2YGn&62Iv`pP(Gz9j(D?Pa$1d^QHG zfpQ>|J^EsQIZM6IW4GF_b426vhz}kDtbCPM`XQr#l>zaW!2$fR0ETR6_DnBd4487vwcS?JW2 zC}^?v%oL6ir9VE6YlDQT4z;qiXo82EjYP#{Z6g6bK0SwU99fuMnbX#__5R|^_n19` zyZdxq`k!LIM&QBVWon3*fzP9g4CgOU(bkV%y>}Dz_bXb*eJ(@vna_L=Em!W#AqJbO zKe9cAIIIxdn>J7ITv9=w_ju%Vyt^zGA&Xh|075AwuPrNwU2%PEy`$$Jhfmv|thdX4 zYP{5UUtvFf`}saE9sKdwCa};TFkqWf9_x4n8|BC5%be(r&LLYkFB$UW+Z|*d!Z*oh zAM&>m4*3S`?N+7fP%b0rxz3ho?0$n|8r$*X_lP>@n zJ4vlBaW*D0aeLi!M9)9vvThRiO6AczGce+iQn+D_oz_(Wj7d0xweoi1;c>!vxwQBVXLBwF^F_g8C* zU!;T38f)kAxO2sXohjy-uB3$Ml=8)e7GD%p8>1$%@920Rgz^Qh!MONdS89(=F?Uq4 zP%<-}y5QKP%1vFrSwUHg$JCiCNl)(cjyAGVrT`llK5;cTzld9clXNU?RG#UN*8&cp zqkkn(PBdsD;LygjAL%!UfGdmNO7T##?vt<4@9#hzDs+U zM-UEoaJ6K@{3$p$9e6bu{gH;YGs|@4)zZxwzQE#MA}cvM*$By_u+sD55PJritS7vh zLEXG@U+?J_w{mDmusb z<+)9+hj82Wq3bZXJMWEUew|@-MBEZja(Pif6matWKzS(dejGSIO=$F39g_!lp#^LC zwOY>jMJS1QrtMPT&h+kMbaj#d{=RZryF>_Rm<3Ilx-hW zGY|sowaZE+lx~_A+Vt&0g>1wsz3p_Y!w^j|^YpU&qMzX;(tr7f3hUL*$HB==cukjBMtSQ85mIY}hjT-2 z7hoG~k=|qo?-!yzUEJrJ7Y?uW1dQ)EO-}mwlk-P3A2qS|yHg zb+O1%zH_IeH7+m^>y#x5zLIz(_>nCW_g5*Y^CzqF=Dqw4!KvZtIjwSgsO>m7c%>0^ z>QC**`|%ayPZs+GX)2lKFHPHUVHUwG2Z@EYu^bs|CI$ zpZC^mPDE4IWu8i^g4xd7>8$3Kp-%-3sBYXv6^6Njd+#hTr+F@G;r;}Jue$&j=aZ7b zEHleyf;2xz4?L+q_DU?j?^ldEhwyG?w4*Bn5YSc;G~;Y8%^kR7HfDRB4txIWXz@r@=(^X5Ym9?V(hKsVEf$5IC0pZkJ4i66<63wGg6biSoCXDEV zYL&v;2&^{J7AB-&)fP~0xh~WiVjT6R=kv@%!J2oP?+v|bHrYMe3Kr91h(X}l1dEDy z+3wHaWdYBfBIUqB`yhx{D8bOG6q4Q##(WC(aWfR2KGnepuW3iE9c!~L-a(WwlJ73Q zB$ppkL=hLs5>$4 zlZwij9`maJ|8BkjSpc|k`9S6%>E**xeN4SI?scX$RP6oAZ+)_Hd3jCb>R{dXu`$1X4%Qmr?ygGeUrm{v|(EE>>=IMn4!PEMDLR1TwRFC_Ra zcVB|cqFZM8>CXKc{RugFi-nhrZcKG68JO=;1oP$X6&XmV+yLJ`%HjCK3LFLF%B%~E zWR#)Yth&gB?jV*ySD`jTS<@Fdok)oqT~lV^BsDVbdD5RnVsED_)wn96(O{!-!|0GS zi{=@0*ZBQhPyX`K%xV<3k$$iwys$)3zI^Ofnx7tgHS~O<9H|S_$Lp9eZC=uGmeHMDx@61uB=>`-dVz&O1XH4Qds> zKJu|2PzJadbmbO=9m@T`5~?M0p1v>Vwzl^NBoODz-g~x$eSVlJVQ29ib9Lb`CA@xoJgQcK9b~>zsN56S*64QT$ZL5`IS?0dzGN$HHCL3WJiE@2 zBush1;Y?6`;|pj7Zd)18Hchh8>>W=8knU$^;jfq?zaU#T!Sv}BcrH$xVClD%e^;1B zjK}o!4DeM0S?_i;L$wCu0}@o5>Mg)ZH*ez8o-MZs7m zRy@*Of@eux&>$O0Cp${B8c*alB5a=ncpYB<6Z3G)M_Px7ioh4Yr4>2_(fcZwc3#JhE-*H^1Q%j@oznLv+GLh)TpcAv{TP#-i{?!Q~4blYP)3ji+&irS4%lxx=4b;+_Tg?l8X$f zz>i&5z(?g>{k`pJqd9zb;daNYiQF(-Dt!&ZK4}Rmkz>tcX+h4>1&Re*AgsK&cdQA1 znQ?bKMn7^O5(L>jbp(CcI?UK=G(n_aMr8@KI(0E<4@|S&lHQ?ki7GUSoT?-%NZ;7c zt;M=q(9{)~!v+^V<%{}&(>gn-2(9G291ChLa~GKRy6+JLGJ`epG+N-t(?9}X>lW1D zT@$s+B!%nEpv7XZ`7mQA@n=8Qd+vU75QIvjX5ACrsbg|mQ4~MvHgz0F%Ky~Nf+#II z()&6Dv*L;3HA@c@7Oh6ULYp!!?U?gHU#5xvYUp(&LK|=Q7ooCxQqNPlup7uM#)g2b zBGIQ&6bUfcCKwRmu*W4=?Yp@IOJ^;1oBXLjabCumJ`$5B>knyI@{h>vzc=5PrjgOj ziuG~JkAknUWHDH2dT18W>)dlaUCGN3LEeGMBvVAJLuIy#uq6E*YS+Q&>co&QQExAVe{sAC&0 z>6Pgpb`sH@*cN&zgZ)gRI$5)lH5F|r;5y&t0C$+2-TX9HQ>V3aVO%VgAD)u{iQ30c zVpX;8y8J06x#(=628_d!pi0z{;h1NqVtKDx%V#iVP$znI>561zV6Rb&FTQH4Vwt78 zi4bVrsj!XoaIse0=zask^GhX|p0;LLsOs9XSDA-;pl^ZRx-`1oTXeY=g~mz;6#Gpx zX>?*o{*G~7?ePMmBg$)c%~ZQL2pYly1@VkP|H|;VL!xQ2b{h=kvJ7f1Gvtheq)#99 z?4)&*0OA(UMyn0meJ?*63Mmnp3R(t40!Vx60p#=R8Il2az6$1JJ<)3}kj zOWjb(H^VQOmtlQ)?r7mwEd8GH&}UN66#TU5jMR)U7oDR-Yj6iXnwll(P75v8qhN;W zG)9PzGG^nxn6;6GoQH6d`TSCfhz?9tq9+3LA}@##Q&(`~4YTx-PiI2eKHvTgyl_bh zcM?gO{RQcEDwVn7HM9@xmrmNbzX0q--X`z##eY@7>RO7_#4&ys3lK;08njV9cWgNx ztxVx_og&T6=q@KMl@qee=eB{;!w178GDL;ceGP?sg!8QKpmrGe9tD2v=1t_bRS*%X z7kd7yV<6b{W$FRP;zJi-UQHci`uB>*yl{j}&t7(nV-w^rWG!Sr=@bdpV1s-K=U&D^ zUb6eyc>1D^fX%+UbnhLcABAo7NIY?X=q^7LL!kOhz?t2n<01GZzQ*el1)A$t3Z+)6 zy&ZqmDd`UTPP6GUj85heb!ds&P3!;#4j3Kut(!{k4?@6~k@a~m=)0S|5xCE2;O8q* z8~9?k1#>W_ndHh8+-{F`8X?~jBquMcI?%lW-f>&c3%GQIvfO~gJmpc)s&fhu@+7w1 z5~r!1;NCOQx(H&cWSv0mf+UHil?kB0LSgdw}KQhN#R5286$vm|}&Ktnpc6ZB~! z*H9>UX;DZdRz1aA%BITqM{5xc&GaH`N9d38Pv?|#87Kc-Z14zR-lXu=I$ve%>Ep z>D;upFaX8G;~(t?PMgNtGR_)p767l16CfVnBsxdd0I41Ym5c`7-;o&sSO2@;w7rF2a z?!QuH6h%RG^n_KqE@q_laF|Tos%QK}(UEEBG%l$-!{i=IvlQiDqAX3c#2Y#2?RrG* z#ftEP{0ETU{*2rUNs~y6_5%us#VDpnEd28mB7T8mO7=F0#4ny)kb%SPJ)%3O-pEu- zy~WNXK5xFvVvYV%H{^eIqhB&P@~xuASOewUEMkOWB=|%6?U#u(={R!4F=dfLg}?I( z``|!f>5RBvozHam%Z&glt;T_Hj%t*@zI_Vxnr|%99zKm)3|ZKxb26D2AsyH3lIRtg zH;V+yZm@v-CU76~9am5>I2w<9Jhjm_^|kkIo%?C!uezyFT~Kb>eqLv*N+tT9i`0sBvndPYeSm<%E}KF2Pgg!YgWeApq4;t z^>H|hIlz3cA@6lW_cfF0iF>9X)<1=H)%hyP>5S@xHi0!9MNY0H!ac%doW$qaUD&=* zp0bEkY?^G(CuC$-_942IB#6x=1F0yDaGK$3yZf@$2JBx;kOP}8;H7zcwoJ+EY?(Ak zA!m!I88j?qdqR}_X}xicEgU5-=YA}OUs{vRf&`T2!p<$9!ADJ>#+k6lYMQ*wvCgSG z$vVjY`oZg!U!%^}&vLy%cAht(z+eY9H0w>hi9UiZ4wv9h<2*>*#fCmQv{}zJ6A!-y z_Q)O=-RjO?lmuK50oyI&lBuocX?Ow-6Q04PYjq9uyH~oL;Ru0hm1YY0t>7vC@vKIk zC7$MP*+M#l<2IWOonsHre-)zI?n7d}p0~5vEU7OEEH0_LDJb21WY4#5ubRWhTdp;Y z_7pf|{rx%7<1AsVvXi);u-H(bcgYB&Op!1O-NK7TrKN6It=EJ!Z7;vK#+mvK}z}}7{7NtfrVMfz?xo;Xj z0v)PeU8Id?M}F;H6?fZ686e=p!=us(zZStmmq{vwZ}5Z|svZ^jLs=vyFqc#*{&nPm z%j<0qMAn$k5Gqw0sLvm*b6VTd{C>TCS6p=J7~N<|enYTK_DaA@L912~L%{z^Ihsh% z{(uZ-(^Ck5)bEKs3vT(+y6aL7|4OV(t?&WY&7ntk=w@-HsSQR_l1G8%`L5wdoil$hDzSN0JpPe^iTHp!q>`b1etZzgu-0JTv9?y>xf3G|=@~ziLRNs3(Pe~}*eOBK+$bxu} z$0Zhy>2M30ALY7F<51Jj3|<#G2Aw9os9zOVW>m(5>mG5bY*rLf*WU7PHoz|wb)j_b zcez%>kRQa;?pO-zUR_~NBxk7$H}02b&b!GOJR@?t9+9bZML>s_T8 zn2;x8I`R2%!-eQq8)KG??rR7&lfzNG3A`HbP}8sD@AiQ&N{h7yq}$#PD(9eC5&vq1}8vY_rc1BZ|Oy4Rg_ zKrq5?7-d#m-=l37Dl<6Q?iM7iZ1Y#)>D?g52fuPBZTXH~XuljycTGTMY(Q z5wnQV=ASX^djCU;vO)4we4gUgt1i(<7O*7MUW^%wUjm7blzCt}H|&a{lB$;ze0eSR zBC+FEua0!DpCSYAZM&V0hvXcE$4%E+p+!5d!oAYsR(sW>M#VjRL%yD=ROTAP5_ru_ z7|DWiv=RKi6fNKL0d7DE604cLR4wfqr8h(>gC}~p!AfiQU@Gr&OPXIs=b8FTQ4C)x zV}@6?2dAOc(amT12gm9CI64g$N+g%Jeb$AV{5sCbVlrmNZTH+H%W{_v4h1Lq_yI?D z=}?){`0jMoUYW2%#O))uL6b}xhf0f!kMKth>)+{v`0Jn@)Vy%*S=6GT7MRZnLZTB| z@0z2+JqxVutZ3tz-E=BnvPF$fCJQa*rO%ew$xAi8vlYCGh}%~$SIR2WqpRQ+Z{)Bl zby!awMqN&0O=%L@-H^dO`>i#j@bunJGbTt|UAG1$?hxn|Z_}3!vBZ~ltum)2fp1hckgj>3QqD?NNSR#oeNm#Vi4*k`2N9uMgQ~`FkSggn_@s4^Hh22ZyqForgg0 z`qFwmwVzD%c)gvyF)r&k2W{5xxRARFtlQm{nm=Q->jq^`7??GO$Y<{0 zVENoPFnRUyJQ4uTLjiv$8MT=|0ToCL(I?gW!?SV<~5=>`5Xsx`KK`rftbw!$8BnH&?ih+xRR} z4O|8U=wrKhkd0C8VzEz=NS9}jQt-0R<(@f=?tj`uaA`i`6JrttJ)6^6aCR(HEYyA# zqK}~HROf5Z#>2 z9Lr6s!kyFa0IH=b-e$q%LwVx9o$>CU!(}WleJ08un4wb-%XnB*ywZ0{bab2o zRScoSv;?Tj-q#0{&(u^JdZLc`?s8##JL=#yn}q2U!On8#7512VeKE z4Ore9EXJihhUf=Y$RphctN|GzgJ_=nF!C?qIp*TMa)ihS*p~}U=lNpC0@{r-sjt7| zI+iCq=&+y7vvbF%Kb%pf5+0kai4=m@j(W;y$9z{F&PsetQ9~CU%1L$4;4le2SZ=UT zY{1ucq1Fv81E-sh3!q2>N)A;Dsow{Ps`P_>CLa(@GWx=brF9`Qc_{OEnUkJl#k3e< zH2K;gZZufO+5&Pg&^de$QC{}Gg4UOpBJj$jeS-0r8e(mzt&*r2Hg<7#WQ;&yY>>zA z^YGY5#Se0ZtLjg|Ju=S1F<`uQXe3YpUg3F{7knS260;fMY;StCY3b>513W#D&I$>| zUS1F;C?_EC0p6w&{6!RUq>gfRADULzq=dJUf!Zsg+TY)dgq^Y5sLD%k2uyi~9Q(X##)zdv~*pc?jlU~uUk@$78s zMt+FjR6lx|;n$QRKzQ(iEE>Js{*p6w>%PO@ln2A!(3~G|S3|5;j9Fpkp-v`reF4PC zCA1ab1?kQA91V#sQBy#Ntfz2#B351if%H$I94R(B8=$UUmia|vtIeMk7FxGMn@q6v z0P=3bervlCLjBr3V74_^tctwGTth{8I2hv+&~624Z?OFl9>xzgUv;0p{=V*^kKz3u zpE3Ts;bdH}OJB(BK2G=Tv;8eB3qShV$)_X7 zD?+?9Vu$H*&ag;Dj_BU=ls+DG!5>)bzqO;8?sg?N+K6meLB~8EI|ets?WU)~0SlRH z?XCWY|5Jynx7}oVy0g%%VL69<6Rd7ES4T=k6iHjhaO~~*7~nC8kzqrI8HfyHdrT}k z5Kyv|+tU!M_ckyzTKb?P`z^q%{f6p+wk&Wz7+%;^+8KV-`#Fz#04YJ5zh6|(Y^n$^ z8l4(TGY;T0fG9<;p8OWNJ_#Y*UY*yOqlHmS6-fQS$ZNc8CE=z^Hu6}PIcf;!?txI( zNR4|p{I&E)Btbg2H!#*9QKD6&ifSolmRTNYJHrpA`(wrMfNAW!Rb_Pnb|oh?JdS}h z+tv_mY(cHnDmEdL(#gKum-cxWX?SiLT{KF$z0=0B@qL5yD1W5s3BB(_0%H{H~NC1{)zUZ-x?tzIFh>xnUqp_wTc{Zdid>$SW%pufO-d2|C9vI z{-*Ap0Ep-T;xL=&9*#WT+OKI_pMQ4k5{#2E8O!PWyxI*u2_)(J`7sqCi6zr|<#|#4I@cljOt{=aKc6X}3)UINbSwM2R@DX#Q6x7qySDYkbl3r)Dx9hS_`fKu1J*O<9r$h^gxb0q*_v2ht0solnJOn4%hYJc2&aLseG~ zE^E0fUiE$i6aH@;H^6oN*qt8{bKQ)Zuce2Qn6sVR3*E!9u(8}8nuV+s(#~HVZw_Ab zj%JD5YRm^Y`h^g32yRanGWw(9w-np1zTYTRDebvD#wQaBcr5QQ9ml8>3othCGAX3V z`8u)12#oQFD-m>espT`WLI_x9?vcvoVKkxQQh~Nu3|Y~+-M6#;jr5lUemQTJ&04#O z@YvOn@N{G(vx$Q3+DvF7v&)3(*4?~9c+%muK?VX!=(Wsr5=V$l)pE?{`p^1f1-pY! zUf$x#bn*F_(G9o4${aMwERED?P{OBu^2@F)iICglXNzb!We?kYEQ23JCjvX}OUVR{ zIK(D9OLdZN16~L zP24zE`70b2+qmuhg!#nIqbQf$E4~Xn`V)+ipo{emQZe{SMf_)~z zI!obfg9HChLO=in{ZsR7o6&R|=V`l(JSkOZVb*BxW#+br0 z-0?|cz1HW9tua=$kLW)!eC3Gx2;cvOA@pPKwfXKNXZLoDGeu*x{_$TMp&>+4UECbx z-nX+JCttbWSXOA(xlF7bE6D;3;o8z{X@8i(9cs~2Kq!c6vcmXM9?l*c(1mK&7}>uC zm@%grTcX#w&XJFXEfz&-HV45eFFPwnHNWP9HxCLDda+*iyED`4Yx~y#%xKJ4J6TI z6O)XKX@9*gmf@B~?xTyK_LXT8j@_N350|~_$PWkHZYN-8n^Tjrm)jwa!%3cei1yB5 z1vgZRi&Dcs=;``Gk=lClLo3Pbz8G?qlDW|aFk1exs@CQ1l)aBQa*?pr$rf!qGuURR zb11_f4SZybvt9{g(*>5!KzTCPT`ao}gnl2LfZ}puy^DU)16r;Aegl%R{am3yky@!o zd#YE;veH5KhH^CtpLOzmTyxm4eXQ_j?D#R@U z(TZZq6ze@&?;ohIHszrMl(uAmtno&fp-BEfX?Tutvq#oNd$~g-q>HltHmAyLN?U6( z;j3u}m*ezkcBBwJ9k2zHL!NO+obAri7AluSaE3qfPi#$VePzH)XfY|I+2Axn$LhHo z%Fe?murZKA&T_JK@fnAb%xa(m3Lk>x?x*0_UjKPi{<|eHl%e8&}l)Au@CMv~TkK)GqSR#e& z^JR$C!E*PCkyKuKbW*F0QUgSiP||3au;8(q7o2aeUA3FD;ry+qVIvKv9CEC~zV6Xg zAyN?TkVtfXr)j=gwQDMOJ_D;oU)-D6#4V!R*A^yeLD*}JL&JUWh(~=c?g}@nfPSK` z;b)O^eIbWYe}Nh(HEEnAKl#e1hmNz|vvtH^!)a9bGr8>5Nlw@z#`9RI0!)6U6AA*Q z>9&tO-zXCZz~<%539YBHy)g{Q^4J{)CkC}=3fif5ckVZS@@+Khz3as^Q5&TepRpgE zi_EZA=uc@+yB|f%lUeAQ#c%*wXL6JY-h&&e^>ZG#Hicd*<&WdLT9KvaY zRr@1(T%S1iP(7nHT$p1VWI9hB1@|Ez38Iu$taX#DH_7B3Z}b~S4LHnB7BesHdMNPE zYiT=Y$7xPg7thvMGIhURzO(Caa;`gj<%4qWcbM991QuYJ=POkww`#r&ph3MHKyc5V zJT0#oOmuszU-yy4w?nDb13tbu=+TVhvi^@57SGcg$E&I$wPtlZ##;fxLhK0Drz}CdCYRQa&sE$nuT&V4OhRT+o+dD!GsD&%(V5MiSW1+6C%7F zS+iM%?JYQun>&|&2Lu21Ea!KbX(U60BtS0g?F0oL&ld(;ZDfs_CB%_0O|S{_&=La~ zG{CsgIQs>&qFoh7t)=+&rYO_y*Voz1H@T_O9R+cmlGI9BM~3e$EK@X)9Pes)xRVY8>nG&kq86E0SWl6pbKK;Iu7G2_1%m8w+k(3n<7hnd6)1H89uQgM@1XwQv@a9;b1JYh`T(Jao8 z6%~prVMTpO$8grbSrEKxtp&d!eN$a*5Q7RwJN{Nnas)EFKe)AMXHJVTpSrlyiHHeq zz0tKdfnG{waxcJ(sASq-nq}^G(@t5vYj@<0=;zG$=H>v$?s!0+t||w*_qcEOA&}?K zCRq(^5_5{D0ZisMZf_tXT7szIz_Ti+>G=%Tal#Ps*K~-=OZrHF>ho!HIK6!Ka{Z9j zj$Y+p3QuE0SWFDb=`#w59NdQIbtvn|gNLegyRinQr{3Jq0hUNMvo|I`^1f&)o85`H z^ZTRl;RbB#xb?1igTeDN)e6^NIFByZhO<7D>mLm@`VN)@N#6+29@F7jdV`HNLKs8L z(k$3#&NREz0SbR5A-Ew6~$^i!=M|Huo4(Qx|sxlr<{ zVf|?2o1aMwnMQ2R%iaYWZ#75Kq%I=KJ@;*%03NgBa$yCV!w}eV^P#xzzg^f(y6)Y! z_i>4HfKZhkG~G-HUP71jOw=p#dJX1p4<5#nQ;D%vy(lDZ9|eOmQ>KaxU5>Fz%%Cq^ zRgdGMT@|P6viiITr9!%P5@?WH)&SENUOB6+|2l}b{XSTtTvl-sS>X#I(xl?EBv|8I zDGsf9P4!)wslp^>cEa^M@#+b+Z*Sjfu?U}w5lk-heg=wQ(E0&Imk{0_&ljqGz+g}o z(Pq4eii<`oCxbU|H|g=X+5*T0_SmhlRh{EDN+|FN!4$jMXB%U`{uj7qvGfU&XiAtV zAD~%=_2=3whhi;YQ|g%-i$V@?>!aYh5{$14ZiS^cr&Br<#b%o_My`8v9PhhHmCi7w zI{Ml@Z3%uyp`lkiQymI7(_~^`GJS+%a(l5F2FV=k1mbM)&jCp=dn6Z0Z+0)(TI27m z1!z-FwO9LMxyAKy2=PnIE!8T|z2x6UoK?yMm74i+(OP2#e#;nEUicYQ=;&@AX7{H0PKBAy-5cAxnqWMP|neG31_9)!n_Q1y0kAh zO%uJWz+@kwtPR9>8qTCgSr*NH?AP(UHR0W`qbAy0b=VE(8roY?|QaX zqmXT?K~+55bEj! zDe??&>cYX_4)mBE_lV=U!3m3R>W4Rh$W#0~FiCrY&pK+hWY6@|FGFx*LlP;OrMyx^ znks1feW_I|+VO2B_Vj*?*iu0bUc?CP)Xy=fWo6zhaLvTTs|dJAzUZ>VutEK9wg~Ht z#_jd@qT*ss;b{0^8B7 z>O(k*rjCYI%h6LxNS3L%HEKj+);QQ5SAA&n%n96 zvCPtVO}4eik20Z8meDQ}tHE<^N9}S^h%M_XFttal0F$Q_wN1k`v?rf^gJc4{$Du?< zu`NvEjndT1n#_8x-5kejTC^&|BH+(iFo|;4HKn#I86sN;aX3j{MH+?voUf ze~EHC+-S86ImgG_jOh+?^Y7oK;cEFj;UMD8Fa4#QY!N)Y1mRFzTDGnSbXWrjSg zT1oTC8Z7+UpYaaa#-KjR<7D!CGgO<+z%aYPspPfi7zo~HWB-&6 zkL_rarNq#h($|b-G7i-CP;*xo-MiyNb0X+SyHjj1ezMO_alReX>w$b<+NUKxiuumi zQkw}(7kmN4fPA(`K4nYDj~*VXgxfdsyB2|@>;Oj-uyW%h1&aJsO3It=9d=?qkrv3P z%!1Xk6Ja#_ngnhkohzsJ*c4A}B5dQp{ol?etXkf$lO~Gh6g!`*j|M-&Ap8&wBGWA%b`=UpFpvwAfy~4Y^CtgFnx((sj%q(e zN-xJLB)SkI9O9~Ppo^nYlPG(G@?{fryDh$i#owW{^E?^CfDtq!X3*W+b_-prf3hp> zJbATRf}|X8JnM)_C9lfp*W&$i@mW>tFi|O2UpPB0ZnWfqP@bLYc9HPLj0FLC^*}n| z3QU_i5Ynb=%$0UBA)!d=EzGx~#X$j4y6jooslpw?mXjubpJ$taT7&67l z0)k!wqfTqia+U8SYW*dl!7)Q#ib~6@*|ZX7@ZJD3b;+e|WSN#p{Pca$CuXNc z?CCQh`A$i8tntpuaLZbH;FjvgWd4K&fyu?5g-wHmPM8hRJwEmoBae64JhIiUY}&;| zijEHELOBD{eE7H@brF2yOx6yE(Asq%QC0tE4BCMc*MybaA($F{I zt@LyFP^JS%hH$`&C5++C6|Z`2N+8HB)LWpL9aKsu)6klR&^tckd$)jq^Rce~GHSUq z09hMBqe9VSx=ydTh1LW}uRX~Fgq}Z!POO&R^rQqI^SnZIDa26b&rFkZm zct+31RIWN^Sb^x&c}xb`$qo5`dF^yi^7qSMguta3^y(0*E9$(I_zQ?DGU!Uv9J490 z7wVDo^9RL!oU#>EAVlJdglu{8y;<$=Z+RdfmVERIc=_*=`-hMIfZ4Nd^u0MH%oy>{ zhd&zTbDiy^_V!%oo1o1b6n2G&7jlfHhWxS{v_=%_+x!{8=N z4cM~egW~-E$o&58DPSG>Zx?t_L_`gS=Ur7oX^w(EZ2zAFxFK zZ7Te|L15-vz$qdZ0h^f5enUA&cyo1Y{kQ_=l=QF93YRwyyMp-2x6l5unHRj+PS8J= z_CGewdw}=fa=_|(`}p60V9)%31L4Z+f4}$p84$ESb^Z(IeYI$qu@wHV{`_6%zkvTc z6#w^CK}Lc2djWZWrHnB8;rG=FK?BnO0r1M9{=RM0b-$8HVdiJOcAo#e(!8Cd08-Aq z=cdj12WdtI2r4OE-F^DC`wJB?M}ctIt(xxSVaY$QM8GJyAL98RdtrqM0CQM(?EIH! z#^1Y&6d>^aWlJyqz{(A?^uKWAWla8G+w&a=7Q+k^Z|=|QMcn|HuGOK<>_6zfA1t_K zhmY&u`0gJLzXD*K<}RD;{ul;KU?U5vTK{=h=mpHLC7dfY?$2{b5cIK!;Xj=P*&wLL zQj^XXs>EJ1gQF`|N_qg}mf?8NM1|>aOV87R%D<=$h!99!fQufXh6wBSHzc^^0>y~W zvaC^S;*mWn8a4gH1(@05DN>v&h0jPBQm5|ISy|%)>f~Pxzl62neph=07ygv`L&D*7 z!|72kV=Je$MAGXu4$WCqa0r-M=lhAx&YUwr>lORuhY3Oxg^WCyH5=xfx1d?~GwJ#V z^m2XvQFvcut3$6wutzlZ^j-{UC`6*yTn#++IqK-jhs5x70bYjZFOPMQcMmr5Wmk~7 zICtvrIRRGEZq&M z3Zun`(!+FeDMic^RfbH|Kwz$$kH5+crIRJ_9L*Rfj@G}7#W9G-Crz(I!Yrg4-oO3r z`UucTP$Y~Azy`_QFE!+s572%bCA*Rdz<52s}Q&NP{1L3E_b6v3c^ z+33326u7fpNFnzJH2sr%qkZ6GD+Qfxd|I+i*3svNVKh<^O8?b-SLAqhovQ*X&yodkzi7Tq1eK#cl7|mzKEK6Bp_I z@^@*RqugwvG5+qkQ$kZcyB8ZC-+12FW!7p}JLazF*rIXU?7rahV>UCf!8mHB*namz zb!@8a_U)2C3_kK>Y7j9x$}cCE94BE~y9I*SeKt69l)-}%PA-2EL! zAQO{fiR@rByghqkIg7DM$4>)r6FI*ZfO?n=M9hPe7zOLGZZ$vJn^{PhEj?~^>h8tH z6lDG9CEEI8SJ%;H_J05ERBg<;L$myu#q11X-UM6A?Ut$g_g{L=Gpu>TAT907x?Ml^ zv#x2Im0#&LS(ax%!V1~^r037&;H@7#K(G-P7Er8jJmOmbFnK-j)k{V{Pd=^hq z6LyROLyzpOB9lb8vU4|?&-g^N@B1mF|JzNnC5!(=p;;nz6|I=kTLVWtEh!rSH5%WgEz;DplN*Q;Zov{46 zDhfSPMS{JI`AewnocJ`ofa3QrSfd`oZ6Ww&+P$jOh5i4&1mOx3Gwe!EDeCt;-M8Hi zu0y`zi`wgdRdh#27IV)l5QhP|uglN*aG7ephwkg&B@euL0r0$v6J)<}{eBIU>}4Ho zX^1EFuZko9c#;BB?7tcSv?M}?|E&Z&GaAJ|71J;tF6Ut=vB5^}ue$FjLB!ca@rXEo zmwKSk z=nE8??LU|R1pIzp*0||;kN(qL*x(`hWuCkm|EIq&h#m5N8HE2vkB=EP8ZHO_uOeY! z?687N|LF)QLL2BWpCJ0biZDq*#3EGxZPb59>i@W=xZzm1Sj?H;7VSL%{=`LOgi8c< Gef|g0c", - "license": "ISC", - "keywords": [ - "x-dash" - ], - "main": "dist/privacy-manager.cjs.js", - "module": "dist/privacy-manager.esm.js", - "browser": "dist/privacy-manager.es5.js", - "style": "dist/privacy-manager.css", - "repository": { - "type": "git", - "url": "https://github.com/Financial-Times/x-dash.git" - }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-privacy-manager", - "engines": { - "node": ">= 6.0.0" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-interaction": "file:../x-interaction", - "classnames": "2.2.6" - }, - "devDependencies": { - "@financial-times/x-rollup": "file:../../packages/x-rollup", - "@financial-times/x-test-utils": "file:../../packages/x-test-utils", - "bower": "1.8.8", - "fetch-mock-jest": "1.3.0", - "sass": "1.26.5" - }, - "scripts": { - "prepare": "bower install && npm run build", - "build": "node rollup.js", - "start": "node rollup.js --watch" - } -} diff --git a/components/x-privacy-manager/readme.md b/components/x-privacy-manager/readme.md deleted file mode 100644 index defc59ea5..000000000 --- a/components/x-privacy-manager/readme.md +++ /dev/null @@ -1,46 +0,0 @@ -# x-privacy-manager - -This module creates an interface giving users the ability to give or withhold consent to the sale of their data under the provisions of the CCPA (California Consumer Protection Act), as a first step towards the FT's journey towards a Unified Privacy solution. - -It is intended for use with Page Kit on FT.com, as a component within FT App, and as a brandable page linked to by Specialist Titles. - -![Privacy Manager UI](docs/ccpa.png) - -## Installation - -This module is compatible with Node 8+ and is distributed on npm. - -```bash -npm install --save @financial-times/x-privacy-manager -``` - -The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. - -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine - - -## Usage - -The components provided by this module are all functions that expect a map of [properties](#properties). They can be used with vanilla JavaScript or JSX (If you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: - -```jsx -import React from 'react'; -import { PrivacyManager } from '@financial-times/x-privacy-manager'; - -// A == B == C -const a = PrivacyManager(props); -const b = ; -const c = React.createElement(PrivacyManager, props); -``` - -All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. - -[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ - -### Properties - -Feature | Type | Notes ------------------|----------|---------------------------- -`consent` | boolean | Any existing preference expressed by the user -`referrer` | string | Used to provide a link back to the referring app's home page -`legislation` | string[] | An array of the applicable legislation IDs diff --git a/components/x-privacy-manager/rollup.js b/components/x-privacy-manager/rollup.js deleted file mode 100644 index d8f76868e..000000000 --- a/components/x-privacy-manager/rollup.js +++ /dev/null @@ -1,4 +0,0 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); - -xRollup({ input: './src/privacy-manager.jsx', pkg }); diff --git a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx deleted file mode 100644 index 14c63bb11..000000000 --- a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx +++ /dev/null @@ -1,137 +0,0 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); -const fetchMock = require('fetch-mock'); - -import { CONSENT_API, BasePrivacyManager, PrivacyManager } from '../privacy-manager'; - -function checkPayload(opts, expected) { - const values = Object.values(JSON.parse(String(opts.body))); - return values.every((v) => v === expected); -} - -describe('x-privacy-manager', () => { - describe('initial state', () => { - beforeEach(() => { - fetchMock.reset(); - fetchMock.mock(CONSENT_API, 200, { delay: 500 }); - }); - - it('defaults to "Allow"', () => { - const subject = mount(); - const inputTrue = subject.find('input[value="true"]').first(); - - // Verify that initial props are correctly reflected - expect(inputTrue.prop('checked')).toBe(true); - }); - - it('highlights explicitly set consent correctly: false', () => { - const subject = mount(); - const inputFalse = subject.find('input[value="false"]').first(); - - // Verify that initial props are correctly reflected - expect(inputFalse.prop('checked')).toBe(true); - }); - - it('highlights explicitly set consent correctly: true', () => { - const subject = mount(); - const inputTrue = subject.find('input[value="true"]').first(); - - // Verify that initial props are correctly reflected - expect(inputTrue.prop('checked')).toBe(true); - }); - - it('handles a change of consent', async () => { - const subject = mount(); - const form = subject.find('form').first(); - const inputTrue = subject.find('input[value="true"]').first(); - const inputFalse = subject.find('input[value="false"]').first(); - - // Switch consent to false and submit form - await inputFalse.prop('onChange')(undefined); - await form.prop('onSubmit')(undefined); - - // Reconcile snapshot with state - subject.update(); - - // Check that fetch was called with the correct values - expect(checkPayload(fetchMock.lastOptions(), false)).toBe(true); - - // Switch consent back to true and resubmit form - await inputTrue.prop('onChange')(undefined); - await form.prop('onSubmit')(undefined); - - // Reconcile snapshot with state - subject.update(); - - // Check that fetch was called with the correct values - expect(checkPayload(fetchMock.lastOptions(), true)).toBe(true); - - // Verify that confimatory nmessage is displayed - const message = subject.find('[data-o-component="o-message"]').first(); - const link = message.find('[data-component="referrer-link"]'); - expect(message).toHaveClassName('o-message--success'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - expect(inputTrue).toHaveProp('checked', true); - }); - }); - - describe('It displays the appropriate messaging', () => { - const defaultProps = { - consent: true, - referrer: 'www.ft.com', - legislation: ['ccpa'], - actions: { - onConsentChange: jest.fn(() => {}), - sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }) - }, - isLoading: false, - _response: undefined - }; - - it('None by default', () => { - const subject = mount(); - - const messages = subject.find('[data-o-component="o-message"]'); - expect(messages).toHaveLength(0); - }); - - it('While loading', () => { - const props = { ...defaultProps, isLoading: true }; - const subject = mount(); - - const messages = subject.find('[data-o-component="o-message"]'); - expect(messages).toHaveLength(1); - expect(messages.first()).toHaveClassName('o-message--neutral'); - }); - - it('On receiving a response with a status of 200', () => { - const _response = { ok: true, status: 200 }; - const props = { ...defaultProps, _response }; - const subject = mount(); - - const messages = subject.find('[data-o-component="o-message"]'); - const message = messages.first(); - - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--success'); - - const link = message.find('[data-component="referrer-link"]'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - }); - - it('On receiving a response with a non-200 status', () => { - const _response = { ok: false, status: 400 }; - const props = { ...defaultProps, _response }; - const subject = mount(); - - const messages = subject.find('[data-o-component="o-message"]'); - const message = messages.first(); - - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--error'); - - const link = message.find('[data-component="referrer-link"]'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - }); - }); -}); diff --git a/components/x-privacy-manager/src/messages.jsx b/components/x-privacy-manager/src/messages.jsx deleted file mode 100644 index 08b3a61b0..000000000 --- a/components/x-privacy-manager/src/messages.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import { h } from '@financial-times/x-engine'; -import s from './privacy-manager.scss'; - -/** - * Provide a way to return to the referrer's homepage - * Potentially a Specialist Title, FT.com or FT App - * - * @param {string} referrer - */ -function renderReferrerLink(referrer) { - let url; - - try { - url = new URL(`https://${referrer}`); - } catch (err) { - // referrer cannot be parsed: omit link - return; - } - - return ( - - Continue to homepage - - ); -} - -function Message({ cls, children }) { - return ( -
      -
      -
      -
      {children}
      -
      -
      -
      - ); -} - -/** - * - * @param {{ - * success: boolean - * referrer: string - * }} props - */ -export function ResponseMessage({ success, referrer }) { - const statusDict = { - true: { - cls: 'o-message--success', - msg: 'Your setting has been saved.' - }, - false: { - cls: 'o-message--error', - msg: 'Your setting could not be saved. Please try again later.' - } - }; - - const status = statusDict[success]; - const cls = `o-message o-message--alert ${status.cls}`; - - return ( - - {status.msg} {renderReferrerLink(referrer)} - - ); -} - -export function LoadingMessage() { - const cls = 'o-message o-message--neutral'; - const spinnerCls = `o-loading o-loading--dark o-loading--small ${s['v-middle']}`; - - return ( - -
      - Loading... -
      - ); -} diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx deleted file mode 100644 index a8e2ccbe8..000000000 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @typedef {{ ok: boolean, status?: number }} _Response - */ - -import { h } from '@financial-times/x-engine'; -import { withActions } from '@financial-times/x-interaction'; - -import s from './privacy-manager.scss'; -import { RadioBtn } from './radio-btn'; -import { LoadingMessage, ResponseMessage } from './messages'; - -// TODO: Replace with the genuine endpoint -export const CONSENT_API = 'https://consent.ft.com'; - -export const withCustomActions = withActions(() => ({ - onConsentChange() { - return ({ consent = true }) => ({ consent: !consent }); - }, - - sendConsent() { - return async ({ isLoading, consent }) => { - if (isLoading) return; - - const body = JSON.stringify({ - demographic: consent, - behavioural: consent, - programmatic: consent - }); - - try { - const res = await fetch(CONSENT_API, { method: 'PATCH', body }); - return { _response: { ok: res.ok } }; - } catch (err) { - return { _response: { ok: false } }; - } - }; - } -})); - -/** - * @param {boolean} isLoading - * @param {_Response} response - * @param {string} referrer - */ -function renderMessage(isLoading, response, referrer) { - if (isLoading) return ; - if (response) return ; - return null; -} - -/** - * @param {{ - * consent?: boolean - * referrer?: string - * legislation?: string[] - * actions: { - * onConsentChange: () => void - * sendConsent: () => Promise<{_response: _Response }> - * }, - * isLoading: boolean - * _response?: _Response - * }} Props - */ -export function BasePrivacyManager({ - consent = true, - referrer, - actions, - isLoading, - _response = undefined -}) { - return ( -
      -

      Do Not Sell My Personal Information

      -
      -

      - CCPA defines the "sale" of personal information in extremely broad terms. For this reason, - the collection of data by advertisers who advertise on our Sites for the purposes of - measuring the effectiveness of their advertising is caught, and requires the ability to - object to the use of your personal information. The data collected by advertisers may - include online identifiers, such as IP address, and interactions with advertisements. -

      -

      - Our advertising Terms and Conditions limit the use of this data by advertisers only to - their own business purposes, including analytics and attribution, not for commercial - purposes. -

      -
      -
      - {renderMessage(isLoading, _response, referrer)} -
      -
      { - event && event.preventDefault(); - return actions.sendConsent(); - }}> -

      Use of my personal information for advertising purposes

      -
      - - Allow - See personalised adverts - - - Block - Only see generic adverts - -
      - -
      -
      -
      - ); -} - -const PrivacyManager = withCustomActions(BasePrivacyManager); - -export { PrivacyManager }; diff --git a/components/x-privacy-manager/src/privacy-manager.scss b/components/x-privacy-manager/src/privacy-manager.scss deleted file mode 100644 index a6f9be10c..000000000 --- a/components/x-privacy-manager/src/privacy-manager.scss +++ /dev/null @@ -1,63 +0,0 @@ -@import 'o-buttons/main'; -@import 'o-colors/main'; -@import 'o-grid/main'; -@import 'o-spacing/main'; -@import 'o-typography/main'; - -.v-middle { - display: inline-block; - vertical-align: middle; -} - -.loading { - composes: v-middle; - margin-left: oSpacingByName(s4) -} - -.consent { - @include oTypographySans($scale: 0, $line-height: 1.6); - - margin: auto; - max-width: map-get($o-grid-layouts, M); -} - -.consent__title { - @include oTypographyHeading($level: 1); -} - -.consent__copy { - margin-top: oSpacingByName(s8); -} - -.divider { - margin-top: oSpacingByName(s8); -} - -.form__title { - @include oTypographyHeading($level: 4); - - margin-top: oSpacingByName(s8); - text-align: center; -} - -.form__controls { - @include oGridRespondTo($from: S) { - display: flex; - } - - margin-top: oSpacingByName(s6); -} - -.form__submit { - @include oButtonsContent( - $opts: ( - 'type': 'primary', - 'size': 'big' - ) - ); - - display: block; - margin: oSpacingByName(s8) auto 0; - padding-left: oSpacingByName(m12); - padding-right: oSpacingByName(m12); -} diff --git a/components/x-privacy-manager/src/radio-btn.jsx b/components/x-privacy-manager/src/radio-btn.jsx deleted file mode 100644 index 2a0f1c612..000000000 --- a/components/x-privacy-manager/src/radio-btn.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { h } from '@financial-times/x-engine'; - -import s from './radio-btn.scss'; - -export function RadioBtn({ value, checked, onChange, children }) { - const id = `ccpa-${value}`; - - return ( -
      - - -
      - ); -} diff --git a/components/x-privacy-manager/src/radio-btn.scss b/components/x-privacy-manager/src/radio-btn.scss deleted file mode 100644 index a25b433a7..000000000 --- a/components/x-privacy-manager/src/radio-btn.scss +++ /dev/null @@ -1,85 +0,0 @@ -@import 'o-colors/main'; -@import 'o-grid/main'; -@import 'o-normalise/main'; -@import 'o-spacing/main'; -@import 'o-typography/main'; - -.input { - @include oNormaliseVisuallyHidden; -} - -.control { - flex: 1; - - & + .control { - margin-top: oSpacingByName(s4); - - @include oGridRespondTo($from: S) { - margin-top: 0; - margin-left: oSpacingByName(s4); - } - } -} - -.label { - transition: background-color 0.1s ease-in, color 0.1s ease-in; - - display: flex; - align-items: center; - - padding: oSpacingByName(s6) oSpacingByName(s4); - border: 2px solid oColorsByName('teal'); - cursor: pointer; - background-color: oColorsByName('white'); - - .input:checked + & { - background-color: oColorsByName('teal'); - color: oColorsByName('white'); - } - - // Since itself is hidden, apply a familiar focus style to the visible

    '); + }); + }); + + describe('title property is missing', () => { + let postWithoutTitle = Object.assign({}, regularPost); + + beforeAll(() => { + delete postWithoutTitle.title + }); + + it('skips rendering of the title', () => { + const liveBlogPost = mount(); - expect(liveBlogPost.html()).toContain('Test title'); + expect(liveBlogPost.html()).not.toContain(''); + }); }); it('renders timestamp', () => { From c90b2a459b5cc8a8a07a85faf1726ad358187444 Mon Sep 17 00:00:00 2001 From: Simon Plenderleith Date: Tue, 25 Aug 2020 22:00:59 +0100 Subject: [PATCH 529/760] Fix height of x-live-blog-post component In order for the `Element.clientHeight` property of the `x-live-blog-post` component's main container element to correctly reflect the height of all child elements, including their margins, we need to ensure that the container fully wraps all elements. Related pull request: https://github.com/Financial-Times/next-article/pull/3929 --- components/x-live-blog-post/src/LiveBlogPost.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index f6f2e9b64..cf5b5de51 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -3,6 +3,7 @@ @import 'o-colors/main'; .live-blog-post { + overflow: auto; border-bottom: 1px solid oColorsMix(black, paper, 20); margin-top: oSpacingByName('s8'); color: oColorsMix(black, paper, 90); From e66961a66723ebd6db5f889314841fe422a337a7 Mon Sep 17 00:00:00 2001 From: Glynn Phillips Date: Fri, 11 Sep 2020 11:46:48 +0100 Subject: [PATCH 530/760] Update x-live-blog-post arguments This component was originally produced whilst we were receiving data from the wordpress cms and we didn't have a schema for the Spark CMS yet. Whilst we are running both systems side by side I have added some new arguments that will in the longer term replace some of the old arguments. I have made this change within the x-dash component to avoid having to map property names in the systems that use this component. This allows us to release this change as a minor release rather than a major breaking change and then release a major when we remove these arguments in the future. --- components/x-live-blog-post/readme.md | 12 +- .../x-live-blog-post/src/LiveBlogPost.jsx | 15 +- .../src/__tests__/LiveBlogPost.test.jsx | 145 ++++++++++++++---- .../x-live-blog-post/storybook/index.jsx | 18 +-- 4 files changed, 138 insertions(+), 52 deletions(-) diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index acce4201c..c2c8719d4 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -36,12 +36,18 @@ All `x-` components are designed to be compatible with a variety of runtimes, no ### Properties +Deprecated properties should only be used when data comes from the Wordpress CMS. +Once we decommission live blogs powered by Wordpress these properties can be removed. + Feature | Type | Notes --------------------|--------|---------------------------- -`postId` | String | Unique id to reference the content +`id` | String | Unique id to reference the content +`postId` | String | Deprecated - Unique id to reference the content `title` | String | Title of the content -`content` | String | Body of the content +`bodyHTML` | String | Body of the content +`content` | String | Deprecated - Body of the content `isBreakingNews` | Bool | When `true` displays "breaking news" tag -`publishedTimestamp`| String | ISO timestamp of publish date +`publishedDate` | String | ISO timestamp of publish date +`publishedTimestamp`| String | Deprecated - ISO timestamp of publish date `articleUrl` | String | Url of the main article that includes this post `showShareButtons` | Bool | default: `false` - Shows social media share buttons when `true` diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 8a9ae6778..61ef6ab08 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -5,10 +5,13 @@ import styles from './LiveBlogPost.scss'; const LiveBlogPost = (props) => { const { - postId, + id, + postId, // Remove once wordpress is no longer in use title, - content, - publishedTimestamp, + content, // Remove once wordpress is no longer in use + bodyHTML, + publishedTimestamp, // Remove once wordpress is no longer in use + publishedDate, isBreakingNews, articleUrl, showShareButtons = false, @@ -17,13 +20,13 @@ const LiveBlogPost = (props) => { return (
    - +
    {isBreakingNews &&
    Breaking news
    } {title &&

    {title}

    } -
    +
    {showShareButtons && - } + }
    ); }; diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index 49512b732..6d16ecf90 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -3,7 +3,7 @@ const { mount } = require('@financial-times/x-test-utils/enzyme'); import { LiveBlogPost } from '../LiveBlogPost'; -const breakingNews = { +const breakingNewsWordpress = { postId: '12345', title: 'Test', content: '

    Test

    ', @@ -13,7 +13,7 @@ const breakingNews = { showShareButtons: true }; -const regularPost = { +const regularPostWordpress = { postId: '12345', title: 'Test title', content: '

    Test body

    ', @@ -23,57 +23,134 @@ const regularPost = { showShareButtons: true } +const breakingNewsSpark = { + id: '12345', + title: 'Test', + bodyHTML: '

    Test

    ', + publishedDate: new Date().toISOString(), + isBreakingNews: true, + articleUrl: 'Https://www.ft.com', + showShareButtons: true +}; + +const regularPostSpark = { + id: '12345', + title: 'Test title', + bodyHTML: '

    Test body

    ', + publishedDate: new Date().toISOString(), + isBreakingNews: false, + articleUrl: 'Https://www.ft.com', + showShareButtons: true +} + describe('x-live-blog-post', () => { - describe('title property exists', () => { - it('renders title', () => { - const liveBlogPost = mount(); + describe('Spark cms', () => { + describe('title property exists', () => { + it('renders title', () => { + const liveBlogPost = mount(); + + expect(liveBlogPost.html()).toContain('Test title'); + expect(liveBlogPost.html()).toContain(''); + }); + }); + + describe('title property is missing', () => { + let postWithoutTitle = Object.assign({}, regularPostSpark); - expect(liveBlogPost.html()).toContain('Test title'); - expect(liveBlogPost.html()).toContain(''); + beforeAll(() => { + delete postWithoutTitle.title + }); + + it('skips rendering of the title', () => { + const liveBlogPost = mount(); + + expect(liveBlogPost.html()).not.toContain(''); + }); }); - }); - describe('title property is missing', () => { - let postWithoutTitle = Object.assign({}, regularPost); + it('renders timestamp', () => { + const liveBlogPost = mount(); - beforeAll(() => { - delete postWithoutTitle.title + expect(liveBlogPost.html()).toContain(regularPostSpark.publishedTimestamp); }); - it('skips rendering of the title', () => { - const liveBlogPost = mount(); + it('renders sharing buttons', () => { + const liveBlogPost = mount(); - expect(liveBlogPost.html()).not.toContain(''); + expect(liveBlogPost.html()).toContain('o-share__icon--linkedin'); }); - }); - it('renders timestamp', () => { - const liveBlogPost = mount(); + it('renders breaking news tag when the post is a breaking news', () => { + const liveBlogPost = mount(); - expect(liveBlogPost.html()).toContain(regularPost.publishedTimestamp); - }); + expect(liveBlogPost.html()).toContain('Breaking news'); + }); - it('renders sharing buttons', () => { - const liveBlogPost = mount(); + it('does not render breaking news tag when the post is not breaking news', () => { + const liveBlogPost = mount(); - expect(liveBlogPost.html()).toContain('o-share__icon--linkedin'); - }); + expect(liveBlogPost.html()).not.toContain('Breaking news'); + }); - it('renders breaking news tag when the post is a breaking news', () => { - const liveBlogPost = mount(); + it('does not escape content html', () => { + const liveBlogPost = mount(); - expect(liveBlogPost.html()).toContain('Breaking news'); + expect(liveBlogPost.html()).toContain('

    Test body

    '); + }); }); - it('does not render breaking news tag when the post is not breaking news', () => { - const liveBlogPost = mount(); + describe('Wordpress cms', () => { + describe('title property exists', () => { + it('renders title', () => { + const liveBlogPost = mount(); - expect(liveBlogPost.html()).not.toContain('Breaking news'); - }); + expect(liveBlogPost.html()).toContain('Test title'); + expect(liveBlogPost.html()).toContain(''); + }); + }); + + describe('title property is missing', () => { + let postWithoutTitle = Object.assign({}, regularPostWordpress); + + beforeAll(() => { + delete postWithoutTitle.title + }); + + it('skips rendering of the title', () => { + const liveBlogPost = mount(); + + expect(liveBlogPost.html()).not.toContain(''); + }); + }); + + it('renders timestamp', () => { + const liveBlogPost = mount(); + + expect(liveBlogPost.html()).toContain(regularPostWordpress.publishedTimestamp); + }); - it('does not escape content html', () => { - const liveBlogPost = mount(); + it('renders sharing buttons', () => { + const liveBlogPost = mount(); - expect(liveBlogPost.html()).toContain('

    Test body

    '); + expect(liveBlogPost.html()).toContain('o-share__icon--linkedin'); + }); + + it('renders breaking news tag when the post is a breaking news', () => { + const liveBlogPost = mount(); + + expect(liveBlogPost.html()).toContain('Breaking news'); + }); + + it('does not render breaking news tag when the post is not breaking news', () => { + const liveBlogPost = mount(); + + expect(liveBlogPost.html()).not.toContain('Breaking news'); + }); + + it('does not escape content html', () => { + const liveBlogPost = mount(); + + expect(liveBlogPost.html()).toContain('

    Test body

    '); + }); }); }); diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index 0b59567d1..e6a3a5fef 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -4,20 +4,20 @@ import { withKnobs, text, boolean } from '@storybook/addon-knobs'; import { LiveBlogPost } from '../src/LiveBlogPost'; const defaultProps = { - postId: 12345, + id: 12345, title: 'Turkey’s virus deaths may be 25% higher than official figure', - content: '

    Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.

    \n

    Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.

    \n

    But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.

    \n

    Read the article here

    \n

    ', + bodyHTML: '

    Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.

    \n

    Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.

    \n

    But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.

    \n

    Read the article here

    \n

    ', isBreakingNews: false, - publishedTimestamp: '2020-05-13T18:52:28.000Z', + publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true, }; const toggleTitle = () => text('Title', defaultProps.title); const toggleShowBreakingNews = () => boolean('Show breaking news', defaultProps.isBreakingNews); -const toggleContent = () => text('Content', defaultProps.content); -const togglePostId = () => text('Post ID', defaultProps.postId); -const togglePublishedTimestamp = () => text('Published timestamp', defaultProps.publishedTimestamp); +const toggleContent = () => text('Body HTML', defaultProps.bodyHTML); +const togglePostId = () => text('ID', defaultProps.id); +const togglePublishedTimestamp = () => text('Published date', defaultProps.publishedDate); const toggleArticleUrl = () => text('Article URL', defaultProps.articleUrl); const toggleShowShareButtons = () => boolean('Show share buttons', defaultProps.showShareButtons); @@ -32,9 +32,9 @@ storiesOf('x-live-blog-post', module) const knobs = { title: toggleTitle(), isBreakingNews: toggleShowBreakingNews(), - content: toggleContent(), - postId: togglePostId(), - publishedTimestamp: togglePublishedTimestamp(), + bodyHTML: toggleContent(), + id: togglePostId(), + publishedDate: togglePublishedTimestamp(), articleUrl: toggleArticleUrl(), showShareButtons: toggleShowShareButtons(), }; From aca8fb1f2c4882177076fda410df9792848df346 Mon Sep 17 00:00:00 2001 From: Glynn Phillips Date: Fri, 11 Sep 2020 14:00:32 +0100 Subject: [PATCH 531/760] Add a data-x-component attribute to live blog post Add a `data-x-component="live-blog-post"` to each instance of the component which can then be used by client side to target the component. An example for this component is that it adds share buttons but the parent application is responsible for initilising the o-share. This allows us to query every post and initilise o-share for that instance. --- components/x-live-blog-post/src/LiveBlogPost.jsx | 2 +- .../x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 8a9ae6778..0ddfde789 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -15,7 +15,7 @@ const LiveBlogPost = (props) => { } = props; return ( -
    +
    diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index 49512b732..dd9c0f7a9 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -76,4 +76,10 @@ describe('x-live-blog-post', () => { expect(liveBlogPost.html()).toContain('

    Test body

    '); }); + + it('adds a data-x-component attribute', () => { + const liveBlogPost = mount(); + + expect(liveBlogPost.html()).toContain('data-x-component="live-blog-post"'); + }); }); From d01e87c68323d1f9b13fc750c8a1a36603f56b1f Mon Sep 17 00:00:00 2001 From: Glynn Phillips Date: Fri, 11 Sep 2020 14:27:35 +0100 Subject: [PATCH 532/760] Update merge conflict in test --- components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index eaa4ae4f4..9b2c70e2a 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -155,7 +155,7 @@ describe('x-live-blog-post', () => { }); it('adds a data-x-component attribute', () => { - const liveBlogPost = mount(); + const liveBlogPost = mount(); expect(liveBlogPost.html()).toContain('data-x-component="live-blog-post"'); }); From ef3a17d0f2508b169c14b4ab5d94e98ab69e1d72 Mon Sep 17 00:00:00 2001 From: Rosario Rascuna Date: Tue, 15 Sep 2020 14:38:12 +0100 Subject: [PATCH 533/760] ACC-640: Change ownership for x-gift-article --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 7017d7b97..98075ac14 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,3 +8,4 @@ components/x-privacy-manager @Financial-Times/ads components/x-teaser @Financial-Times/content-discovery components/x-teaser-timeline @Financial-Times/content-discovery components/x-live-blog-post @Financial-Times/content-innovation +components/x-gift-article @Financial-Times/accounts From d2f9daa569a5ca6d135a17037eebcf45576c7ca0 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Tue, 21 Jul 2020 11:41:21 +0100 Subject: [PATCH 534/760] Initialize x-live-blog-wrapper component --- .storybook/config.js | 1 + components/x-live-blog-wrapper/.npmignore | 3 ++ components/x-live-blog-wrapper/package.json | 34 +++++++++++++++ components/x-live-blog-wrapper/readme.md | 43 +++++++++++++++++++ components/x-live-blog-wrapper/rollup.js | 4 ++ .../src/LiveBlogWrapper.jsx | 10 +++++ .../x-live-blog-wrapper/storybook/index.jsx | 25 +++++++++++ 7 files changed, 120 insertions(+) create mode 100644 components/x-live-blog-wrapper/.npmignore create mode 100644 components/x-live-blog-wrapper/package.json create mode 100644 components/x-live-blog-wrapper/readme.md create mode 100644 components/x-live-blog-wrapper/rollup.js create mode 100644 components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx create mode 100644 components/x-live-blog-wrapper/storybook/index.jsx diff --git a/.storybook/config.js b/.storybook/config.js index 6579545a1..c6087005c 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -15,4 +15,5 @@ configure(() => { require('../components/x-increment/storybook/index.jsx'); require('../components/x-follow-button/storybook/index.jsx'); require('../components/x-live-blog-post/storybook/index.jsx'); + require('../components/x-live-blog-wrapper/storybook/index.jsx'); }, module); diff --git a/components/x-live-blog-wrapper/.npmignore b/components/x-live-blog-wrapper/.npmignore new file mode 100644 index 000000000..a44a9e753 --- /dev/null +++ b/components/x-live-blog-wrapper/.npmignore @@ -0,0 +1,3 @@ +src/ +stories/ +rollup.js diff --git a/components/x-live-blog-wrapper/package.json b/components/x-live-blog-wrapper/package.json new file mode 100644 index 000000000..36ed5acdb --- /dev/null +++ b/components/x-live-blog-wrapper/package.json @@ -0,0 +1,34 @@ +{ + "name": "@financial-times/x-live-blog-wrapper", + "version": "0.0.0", + "description": "", + "main": "dist/LiveBlogWrapper.cjs.js", + "module": "dist/LiveBlogWrapper.esm.js", + "browser": "dist/LiveBlogWrapper.es5.js", + "scripts": { + "build": "node rollup.js", + "start": "node rollup.js --watch" + }, + "keywords": [ + "x-dash" + ], + "author": "", + "license": "ISC", + "dependencies": { + "@financial-times/x-engine": "file:../../packages/x-engine" + }, + "devDependencies": { + "@financial-times/x-rollup": "file:../../packages/x-rollup" + }, + "repository": { + "type": "git", + "url": "https://github.com/Financial-Times/x-dash.git" + }, + "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-live-blog-wrapper", + "engines": { + "node": ">= 6.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md new file mode 100644 index 000000000..4464ba1b1 --- /dev/null +++ b/components/x-live-blog-wrapper/readme.md @@ -0,0 +1,43 @@ +# x-liveblog-wrapper + +This module has these features and scope. + + +## Installation + +This module is compatible with Node 6+ and is distributed on npm. + +```bash +npm install --save @financial-times/x-liveblog-wrapper +``` + +The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. + +[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine + + +## Usage + +The components provided by this module are all functions that expect a map of [properties](#properties). They can be used with vanilla JavaScript or JSX (If you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: + +```jsx +import React from 'react'; +import { Live-blog-wrapper } from '@financial-times/x-live-blog-wrapper'; + +// A == B == C +const a = Live-blog-wrapper(props); +const b = ; +const c = React.createElement(Live-blog-wrapper, props); +``` + +All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. + +[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ + +### Properties + +Feature | Type | Notes +-----------------|--------|---------------------------- +`propertyName1` | String | +`propertyName2` | String | +`propertyName2` | String | diff --git a/components/x-live-blog-wrapper/rollup.js b/components/x-live-blog-wrapper/rollup.js new file mode 100644 index 000000000..1a89a8955 --- /dev/null +++ b/components/x-live-blog-wrapper/rollup.js @@ -0,0 +1,4 @@ +const xRollup = require('@financial-times/x-rollup'); +const pkg = require('./package.json'); + +xRollup({ input: './src/LiveBlogWrapper.jsx', pkg }); diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx new file mode 100644 index 000000000..80d0f952b --- /dev/null +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -0,0 +1,10 @@ +import { h } from '@financial-times/x-engine'; + +const LiveBlogWrapper = (props) => ( +
    +

    Welcome to x-live-blog-wrapper

    +

    {props.message}

    +
    +); + +export { LiveBlogWrapper }; diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx new file mode 100644 index 000000000..9144ddd87 --- /dev/null +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { withKnobs, text } from '@storybook/addon-knobs'; +import { LiveBlogWrapper } from '../src/LiveBlogWrapper'; + +const defaultProps = { + message: 'Test' +}; + +const toggleMessage = () => text('Message', defaultProps.message); + +storiesOf('x-live-blog-wrapper', module) + .addDecorator(withKnobs) + .addParameters({ + knobs: { + escapeHTML: false + } + }) + .add('Content Body', () => { + const knobs = { + message: toggleMessage(), + }; + + return ; + }); From cba61ab84d8d44801bb036363a5397b49bd0cbfb Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Tue, 21 Jul 2020 16:19:34 +0100 Subject: [PATCH 535/760] Render initial post elements --- components/x-live-blog-wrapper/package.json | 6 ++-- .../src/LiveBlogWrapper.jsx | 15 ++++---- .../src/__tests__/LiveBlogWrapper.test.jsx | 34 +++++++++++++++++++ .../x-live-blog-wrapper/storybook/index.jsx | 31 ++++++++++++++++- 4 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx diff --git a/components/x-live-blog-wrapper/package.json b/components/x-live-blog-wrapper/package.json index 36ed5acdb..79fb8fcd6 100644 --- a/components/x-live-blog-wrapper/package.json +++ b/components/x-live-blog-wrapper/package.json @@ -15,10 +15,12 @@ "author": "", "license": "ISC", "dependencies": { - "@financial-times/x-engine": "file:../../packages/x-engine" + "@financial-times/x-engine": "file:../../packages/x-engine", + "@financial-times/x-live-blog-post": "file:../x-live-blog-post" }, "devDependencies": { - "@financial-times/x-rollup": "file:../../packages/x-rollup" + "@financial-times/x-rollup": "file:../../packages/x-rollup", + "@financial-times/x-test-utils": "file:../../packages/x-test-utils" }, "repository": { "type": "git", diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 80d0f952b..e3277e205 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -1,10 +1,13 @@ import { h } from '@financial-times/x-engine'; +import { LiveBlogPost } from '@financial-times/x-live-blog-post'; -const LiveBlogWrapper = (props) => ( -
    -

    Welcome to x-live-blog-wrapper

    -

    {props.message}

    -
    -); +const LiveBlogWrapper = ({ posts = [] }) => { + const postElements = posts.map((post) => ); + return ( +
    + {postElements} +
    + ); +}; export { LiveBlogWrapper }; diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx new file mode 100644 index 000000000..69fb87302 --- /dev/null +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -0,0 +1,34 @@ +const { h } = require('@financial-times/x-engine'); +const { mount } = require('@financial-times/x-test-utils/enzyme'); + +import { LiveBlogWrapper } from '../LiveBlogWrapper'; + +const post1 = { + postId: '1', + title: 'Post 1 Title', + content: '

    Post 1

    ', + publishedTimestamp: new Date().toISOString(), + isBreakingNews: true, + articleUrl: 'Https://www.ft.com', + showShareButtons: true +}; + +const post2 = { + postId: '2', + title: 'Post 2 Title', + content: '

    Post 2>

    ', + publishedTimestamp: new Date().toISOString(), + isBreakingNews: false, + articleUrl: 'Https://www.ft.com', + showShareButtons: true +} + +describe('x-live-blog-wrapper', () => { + it('renders initial posts', () => { + const posts = [ post1, post2 ]; + const liveBlogWrapper = mount(); + + expect(liveBlogWrapper.html()).toContain('Post 1 Title'); + expect(liveBlogWrapper.html()).toContain('Post 2 Title'); + }); +}); diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 9144ddd87..0b2b5658a 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -4,7 +4,36 @@ import { withKnobs, text } from '@storybook/addon-knobs'; import { LiveBlogWrapper } from '../src/LiveBlogWrapper'; const defaultProps = { - message: 'Test' + message: 'Test', + posts: [ + { + postId: 12345, + title: 'Title 1', + content: '

    Post 1

    ', + isBreakingNews: false, + publishedTimestamp: '2020-05-13T18:52:28.000Z', + articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', + showShareButtons: true, + }, + { + postId: 12346, + title: 'Title 2', + content: '

    Post 2

    ', + isBreakingNews: true, + publishedTimestamp: '2020-05-13T19:52:28.000Z', + articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', + showShareButtons: true, + }, + { + postId: 12347, + title: 'Title 3', + content: '

    Post 3

    ', + isBreakingNews: false, + publishedTimestamp: '2020-05-13T20:52:28.000Z', + articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', + showShareButtons: true, + } + ] }; const toggleMessage = () => text('Message', defaultProps.message); From 07ccaf84392496e72b8ca7e797101ea472d7fa69 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Thu, 23 Jul 2020 15:33:24 +0100 Subject: [PATCH 536/760] Updates with test buttons --- components/x-live-blog-wrapper/package.json | 3 +- .../src/LiveBlogWrapper.jsx | 67 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/components/x-live-blog-wrapper/package.json b/components/x-live-blog-wrapper/package.json index 79fb8fcd6..be299abd2 100644 --- a/components/x-live-blog-wrapper/package.json +++ b/components/x-live-blog-wrapper/package.json @@ -16,7 +16,8 @@ "license": "ISC", "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", - "@financial-times/x-live-blog-post": "file:../x-live-blog-post" + "@financial-times/x-live-blog-post": "file:../x-live-blog-post", + "@financial-times/x-interaction": "file:../x-interaction" }, "devDependencies": { "@financial-times/x-rollup": "file:../../packages/x-rollup", diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index e3277e205..35cfda390 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -1,13 +1,76 @@ import { h } from '@financial-times/x-engine'; import { LiveBlogPost } from '@financial-times/x-live-blog-post'; +import { withActions } from '@financial-times/x-interaction'; + +const liveBlogWrapperActions = withActions({ + addPost (post) { + return ({ posts }) => { + const updatedPosts = [ post, ...posts ]; + + return { posts: updatedPosts }; + }; + }, + + updatePost (updated) { + return ({ posts }) => { + const updatedPosts = posts.map( + (post) => post.postId !== updated.postId ? post : updated + ); + + return { posts: updatedPosts }; + }; + }, + + deletePost (postId) { + return ({ posts }) => { + const updatedPosts = posts.filter((post) => post.postId !== postId); + return { posts: updatedPosts }; + } + } +}); + +const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], actions }) => { + const addPostTest = () => { + const newPost = { + postId: `${Math.random()}`, + title: `Title ${Math.random()}`, + content: `

    Post ${Math.random()}

    `, + isBreakingNews: false, + publishedTimestamp: '2020-05-13T20:52:28.000Z', + articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', + showShareButtons: true, + } + + actions.addPost(newPost); + }; + + const updatePostTest = () => { + const updatedPost = { + postId: 12346, + title: 'Title 2 - Updated', + content: '

    Post 2 - Updated

    ', + isBreakingNews: true, + publishedTimestamp: '2020-05-13T19:52:28.000Z', + articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', + showShareButtons: true, + }; + + actions.updatePost(updatedPost); + }; + + const deletePostTest = () => { + actions.deletePost(12345); + }; -const LiveBlogWrapper = ({ posts = [] }) => { const postElements = posts.map((post) => ); return (
    + + + {postElements}
    ); -}; +}); export { LiveBlogWrapper }; From 54231273d24cbb381db4bdde92c72b276cb94b4d Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:14:27 +0100 Subject: [PATCH 537/760] Add hydration data to live blogs wrapper --- components/x-live-blog-wrapper/rollup.js | 2 +- .../src/HydratedLiveBlogWrapper.jsx | 16 ++++++ .../src/LiveBlogWrapper.jsx | 52 ++++++++++++++----- .../x-live-blog-wrapper/storybook/index.jsx | 4 +- 4 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx diff --git a/components/x-live-blog-wrapper/rollup.js b/components/x-live-blog-wrapper/rollup.js index 1a89a8955..c2ecbfaa0 100644 --- a/components/x-live-blog-wrapper/rollup.js +++ b/components/x-live-blog-wrapper/rollup.js @@ -1,4 +1,4 @@ const xRollup = require('@financial-times/x-rollup'); const pkg = require('./package.json'); -xRollup({ input: './src/LiveBlogWrapper.jsx', pkg }); +xRollup({ input: './src/HydratedLiveBlogWrapper.jsx', pkg }); diff --git a/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx new file mode 100644 index 000000000..44a546b15 --- /dev/null +++ b/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx @@ -0,0 +1,16 @@ +import { h, Fragment } from '@financial-times/x-engine'; +import { LiveBlogWrapper } from "./LiveBlogWrapper"; +import { Serialiser, HydrationData } from '@financial-times/x-interaction'; + +const HydratedLiveBlogWrapper = (props) => { + const serialiser = new Serialiser(); + + return ( + + + + + ) +}; + +export { HydratedLiveBlogWrapper }; diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 35cfda390..ea77c432f 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -1,9 +1,9 @@ -import { h } from '@financial-times/x-engine'; +import { h, Fragment } from '@financial-times/x-engine'; import { LiveBlogPost } from '@financial-times/x-live-blog-post'; import { withActions } from '@financial-times/x-interaction'; const liveBlogWrapperActions = withActions({ - addPost (post) { + insertPost (post) { return ({ posts }) => { const updatedPosts = [ post, ...posts ]; @@ -14,7 +14,7 @@ const liveBlogWrapperActions = withActions({ updatePost (updated) { return ({ posts }) => { const updatedPosts = posts.map( - (post) => post.postId !== updated.postId ? post : updated + post => post.postId === updated.postId ? updated : post ); return { posts: updatedPosts }; @@ -26,10 +26,26 @@ const liveBlogWrapperActions = withActions({ const updatedPosts = posts.filter((post) => post.postId !== postId); return { posts: updatedPosts }; } + }, + + startListeningToLiveEvents(blogPath) { + // TODO: this is a pseudo implmentation at the moment. we need to parse and process posts from the events first. + + const source = new EventSource(`https://next-live-event.ft.com?eventid=${blogPath}&formatted=false`, { withCredentials: true }); + + source.addEventListener('msg', this.insertPost); + + source.addEventListener('editmsg', this.updatePost); + + source.addEventListener('delete', this.deletePost); + + // TODO: do we handle live blog status updates in this component? + // source.addEventListener('close', updateLiveBlogStatus); } + }); -const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], actions }) => { +const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], blogPath, articleUrl, showShareButtons, actions }) => { const addPostTest = () => { const newPost = { postId: `${Math.random()}`, @@ -41,7 +57,14 @@ const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], actions }) => { showShareButtons: true, } - actions.addPost(newPost); + actions.insertPost(newPost); + + // window.setTimeout needed here because the internal state of this component will update + // after this function returns. this means document updates will occur only after this line + // executes. + // consumer app will need to consume this event after the component is rendered. therefore, + // we defer dispatching of this event. + window.setTimeout(() => document.dispatchEvent(new CustomEvent('LiveBlogWrapper.INSERT_POST', { detail: { post: newPost } })), 0); }; const updatePostTest = () => { @@ -62,14 +85,19 @@ const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], actions }) => { actions.deletePost(12345); }; - const postElements = posts.map((post) => ); + const postElements = posts.map((post) => ); return ( -
    - - - - {postElements} -
    + + +
    + + + +
    +
    + {postElements} +
    +
    ); }); diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 0b2b5658a..2a994549d 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { withKnobs, text } from '@storybook/addon-knobs'; -import { LiveBlogWrapper } from '../src/LiveBlogWrapper'; +import { HydratedLiveBlogWrapper } from '../src/HydratedLiveBlogWrapper'; const defaultProps = { message: 'Test', @@ -50,5 +50,5 @@ storiesOf('x-live-blog-wrapper', module) message: toggleMessage(), }; - return ; + return ; }); From fc17049f6792d7283ebfee73be4e8928b1249109 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:14:53 +0100 Subject: [PATCH 538/760] Add static class to LiveBlogPost article element --- components/x-live-blog-post/src/LiveBlogPost.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 1f8617a27..afaf25280 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -18,7 +18,10 @@ const LiveBlogPost = (props) => { } = props; return ( -
    +
    From a3681d49678bffb13afc25c1ea07739f25248b4f Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 29 Jul 2020 17:04:53 +0100 Subject: [PATCH 539/760] Listen to live events --- .../src/LiveBlogWrapper.jsx | 145 +++++++++++------- 1 file changed, 86 insertions(+), 59 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index ea77c432f..ad5bbebf1 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -2,6 +2,47 @@ import { h, Fragment } from '@financial-times/x-engine'; import { LiveBlogPost } from '@financial-times/x-live-blog-post'; import { withActions } from '@financial-times/x-interaction'; +const parsePost = (event) => { + const post = JSON.parse(event.data); + + if (!post || !post.mid || !post.textrendered) { + return; + } + + const textElement = document.createElement('div'); + textElement.innerHTML = post.textrendered; + + const titleElement = textElement.querySelector('p strong'); + const breakingNewsElement = textElement.querySelector('img[src*="breaking_news.gif"]'); + const isBreakingNews = Boolean(breakingNewsElement); + + if (isBreakingNews) { + textElement.removeChild(breakingNewsElement.parentNode); + } + + textElement.removeChild(titleElement.parentNode); + + const content = textElement.innerHTML.trim(); + const title = titleElement.textContent; + const publishedTimestamp = (new Date(Number(post.emb) * 1000)).toISOString(); + + return { + postId: post.mid, + author: post.authordisplayname, + publishedTimestamp, + isBreakingNews, + isKeyEvent: Boolean(post.keytext), + title, + content + }; +}; + +const dispatchLiveUpdateEvent = (eventType, data) => { + // consumer app will need to consume this event after the component is rendered. therefore, + // we defer dispatching of this event. + window.setTimeout(() => document.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0); +}; + const liveBlogWrapperActions = withActions({ insertPost (post) { return ({ posts }) => { @@ -25,79 +66,65 @@ const liveBlogWrapperActions = withActions({ return ({ posts }) => { const updatedPosts = posts.filter((post) => post.postId !== postId); return { posts: updatedPosts }; - } + }; }, - startListeningToLiveEvents(blogPath) { - // TODO: this is a pseudo implmentation at the moment. we need to parse and process posts from the events first. + startListeningToLiveEvents () { + } +}); - const source = new EventSource(`https://next-live-event.ft.com?eventid=${blogPath}&formatted=false`, { withCredentials: true }); +const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], articleUrl, showShareButtons, blogPath, isLoading, actions }) => { + if (isLoading) { + // TODO: currently we add event listeners on every render of this component. we need to do it only once. + // possibly make this a separate component for client apps to render. - source.addEventListener('msg', this.insertPost); + // const eventSource = new EventSource(`https://next-live-event.ft.com?eventid=${blogPath}&formatted=false`, { withCredentials: true }); + const eventSource = new EventSource(`http://localhost:3000/events`, { withCredentials: false }); - source.addEventListener('editmsg', this.updatePost); + eventSource.addEventListener('msg', (event) => { + const post = parsePost(event); - source.addEventListener('delete', this.deletePost); + if (!post) { + return; + } - // TODO: do we handle live blog status updates in this component? - // source.addEventListener('close', updateLiveBlogStatus); - } + actions.insertPost(post); + dispatchLiveUpdateEvent('LiveBlogWrapper.INSERT_POST', { post }); + }); -}); + eventSource.addEventListener('editmsg', (event) => { + const post = parsePost(event); -const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], blogPath, articleUrl, showShareButtons, actions }) => { - const addPostTest = () => { - const newPost = { - postId: `${Math.random()}`, - title: `Title ${Math.random()}`, - content: `

    Post ${Math.random()}

    `, - isBreakingNews: false, - publishedTimestamp: '2020-05-13T20:52:28.000Z', - articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', - showShareButtons: true, - } - - actions.insertPost(newPost); - - // window.setTimeout needed here because the internal state of this component will update - // after this function returns. this means document updates will occur only after this line - // executes. - // consumer app will need to consume this event after the component is rendered. therefore, - // we defer dispatching of this event. - window.setTimeout(() => document.dispatchEvent(new CustomEvent('LiveBlogWrapper.INSERT_POST', { detail: { post: newPost } })), 0); - }; + if (!post) { + return; + } - const updatePostTest = () => { - const updatedPost = { - postId: 12346, - title: 'Title 2 - Updated', - content: '

    Post 2 - Updated

    ', - isBreakingNews: true, - publishedTimestamp: '2020-05-13T19:52:28.000Z', - articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', - showShareButtons: true, - }; + actions.updatePost(post); + dispatchLiveUpdateEvent('LiveBlogWrapper.UPDATE_POST', { post }); + }); - actions.updatePost(updatedPost); - }; + eventSource.addEventListener('delete', (event) => { + const post = parsePost(event); - const deletePostTest = () => { - actions.deletePost(12345); - }; + if (!post) { + return; + } + + actions.deletePost(post.postId); + dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.postId }); + }); + + // TODO: do we handle live blog status updates in this component? + eventSource.addEventListener('close', (event) => { + // TODO + }); + } - const postElements = posts.map((post) => ); + const postElements = posts.map((post) => ); return ( - - -
    - - - -
    -
    - {postElements} -
    -
    +
    + {postElements} +
    ); }); From ea5b38107e44e378b49d3133820827b42cb5bde5 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 29 Jul 2020 21:43:53 +0100 Subject: [PATCH 540/760] Separate live update logic from component This should be initialised at the client side after hydrating the wrapper component --- .../src/HydratedLiveBlogWrapper.jsx | 3 +- .../src/LiveBlogWrapper.jsx | 92 +---------------- .../src/LiveEventListener.js | 98 +++++++++++++++++++ 3 files changed, 101 insertions(+), 92 deletions(-) create mode 100644 components/x-live-blog-wrapper/src/LiveEventListener.js diff --git a/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx index 44a546b15..024f379a7 100644 --- a/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx @@ -1,6 +1,7 @@ import { h, Fragment } from '@financial-times/x-engine'; import { LiveBlogWrapper } from "./LiveBlogWrapper"; import { Serialiser, HydrationData } from '@financial-times/x-interaction'; +import { listenToLiveBlogEvents } from './LiveEventListener'; const HydratedLiveBlogWrapper = (props) => { const serialiser = new Serialiser(); @@ -13,4 +14,4 @@ const HydratedLiveBlogWrapper = (props) => { ) }; -export { HydratedLiveBlogWrapper }; +export { HydratedLiveBlogWrapper, listenToLiveBlogEvents }; diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index ad5bbebf1..5eb1cdeb7 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -2,47 +2,6 @@ import { h, Fragment } from '@financial-times/x-engine'; import { LiveBlogPost } from '@financial-times/x-live-blog-post'; import { withActions } from '@financial-times/x-interaction'; -const parsePost = (event) => { - const post = JSON.parse(event.data); - - if (!post || !post.mid || !post.textrendered) { - return; - } - - const textElement = document.createElement('div'); - textElement.innerHTML = post.textrendered; - - const titleElement = textElement.querySelector('p strong'); - const breakingNewsElement = textElement.querySelector('img[src*="breaking_news.gif"]'); - const isBreakingNews = Boolean(breakingNewsElement); - - if (isBreakingNews) { - textElement.removeChild(breakingNewsElement.parentNode); - } - - textElement.removeChild(titleElement.parentNode); - - const content = textElement.innerHTML.trim(); - const title = titleElement.textContent; - const publishedTimestamp = (new Date(Number(post.emb) * 1000)).toISOString(); - - return { - postId: post.mid, - author: post.authordisplayname, - publishedTimestamp, - isBreakingNews, - isKeyEvent: Boolean(post.keytext), - title, - content - }; -}; - -const dispatchLiveUpdateEvent = (eventType, data) => { - // consumer app will need to consume this event after the component is rendered. therefore, - // we defer dispatching of this event. - window.setTimeout(() => document.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0); -}; - const liveBlogWrapperActions = withActions({ insertPost (post) { return ({ posts }) => { @@ -67,59 +26,10 @@ const liveBlogWrapperActions = withActions({ const updatedPosts = posts.filter((post) => post.postId !== postId); return { posts: updatedPosts }; }; - }, - - startListeningToLiveEvents () { } }); -const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], articleUrl, showShareButtons, blogPath, isLoading, actions }) => { - if (isLoading) { - // TODO: currently we add event listeners on every render of this component. we need to do it only once. - // possibly make this a separate component for client apps to render. - - // const eventSource = new EventSource(`https://next-live-event.ft.com?eventid=${blogPath}&formatted=false`, { withCredentials: true }); - const eventSource = new EventSource(`http://localhost:3000/events`, { withCredentials: false }); - - eventSource.addEventListener('msg', (event) => { - const post = parsePost(event); - - if (!post) { - return; - } - - actions.insertPost(post); - dispatchLiveUpdateEvent('LiveBlogWrapper.INSERT_POST', { post }); - }); - - eventSource.addEventListener('editmsg', (event) => { - const post = parsePost(event); - - if (!post) { - return; - } - - actions.updatePost(post); - dispatchLiveUpdateEvent('LiveBlogWrapper.UPDATE_POST', { post }); - }); - - eventSource.addEventListener('delete', (event) => { - const post = parsePost(event); - - if (!post) { - return; - } - - actions.deletePost(post.postId); - dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.postId }); - }); - - // TODO: do we handle live blog status updates in this component? - eventSource.addEventListener('close', (event) => { - // TODO - }); - } - +const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], articleUrl, showShareButtons }) => { const postElements = posts.map((post) => ); return (
    diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js new file mode 100644 index 000000000..32964ca52 --- /dev/null +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -0,0 +1,98 @@ +// TODO: parsing posts will not be needed when we use v2 route on next-live-events-api +const parsePost = (event) => { + const post = JSON.parse(event.data); + + if (!post || !post.mid || !post.textrendered) { + return; + } + + const textElement = document.createElement('div'); + textElement.innerHTML = post.textrendered; + + const titleElement = textElement.querySelector('p strong'); + const breakingNewsElement = textElement.querySelector('img[src*="breaking_news.gif"]'); + const isBreakingNews = Boolean(breakingNewsElement); + + if (isBreakingNews) { + textElement.removeChild(breakingNewsElement.parentNode); + } + + textElement.removeChild(titleElement.parentNode); + + const content = textElement.innerHTML.trim(); + const title = titleElement.textContent; + const publishedTimestamp = (new Date(Number(post.emb) * 1000)).toISOString(); + + return { + postId: post.mid, + author: post.authordisplayname, + publishedTimestamp, + isBreakingNews, + isKeyEvent: Boolean(post.keytext), + title, + content + }; +}; + +const dispatchLiveUpdateEvent = (eventType, data) => { + // consumer app will need to consume this event after the component is rendered. therefore, + // we defer dispatching of this event. + window.setTimeout(() => document.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0); +}; + +const listenToLiveBlogEvents = ({ liveBlogWrapperId, blogPath }) => { + const invokeAction = (action, args) => { + const wrapper = document.querySelector(`[data-x-dash-id="${liveBlogWrapperId}"]`); + wrapper.dispatchEvent( + new CustomEvent( + 'x-interaction.trigger-action', + { detail: { action, args } } + ) + ); + }; + + // TODO: use next-live-events-api v2 routes + // const eventSource = new EventSource(`https://next-live-event.ft.com?eventid=${blogPath}&formatted=false`, { withCredentials: true }); + const eventSource = new EventSource(`http://localhost:3000/events`, { withCredentials: false }); + + eventSource.addEventListener('msg', (event) => { + const post = parsePost(event); + + if (!post) { + return; + } + + invokeAction('insertPost', [ post ]); + dispatchLiveUpdateEvent('LiveBlogWrapper.INSERT_POST', { post }); + }); + + eventSource.addEventListener('editmsg', (event) => { + const post = parsePost(event); + + if (!post) { + return; + } + + invokeAction('updatePost', [ post ]); + dispatchLiveUpdateEvent('LiveBlogWrapper.UPDATE_POST', { post }); + }); + + eventSource.addEventListener('delete', (event) => { + const post = parsePost(event); + + if (!post) { + return; + } + + invokeAction('deletePost', [ post.postId ]) + dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.postId }); + }); + + // TODO: do we handle live blog status updates in this component? + eventSource.addEventListener('close', (event) => { + // TODO + }); + +}; + +export { listenToLiveBlogEvents }; From 3822d997495c89826a86105135bbef2095fafa9b Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Thu, 6 Aug 2020 11:44:22 +0100 Subject: [PATCH 541/760] Use next-live-event-api v2 endpoint to listen to live updates --- .../src/LiveEventListener.js | 44 ++++--------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index 32964ca52..b097f0e3b 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -1,37 +1,11 @@ -// TODO: parsing posts will not be needed when we use v2 route on next-live-events-api const parsePost = (event) => { const post = JSON.parse(event.data); - if (!post || !post.mid || !post.textrendered) { + if (!post || !post.postId) { return; } - const textElement = document.createElement('div'); - textElement.innerHTML = post.textrendered; - - const titleElement = textElement.querySelector('p strong'); - const breakingNewsElement = textElement.querySelector('img[src*="breaking_news.gif"]'); - const isBreakingNews = Boolean(breakingNewsElement); - - if (isBreakingNews) { - textElement.removeChild(breakingNewsElement.parentNode); - } - - textElement.removeChild(titleElement.parentNode); - - const content = textElement.innerHTML.trim(); - const title = titleElement.textContent; - const publishedTimestamp = (new Date(Number(post.emb) * 1000)).toISOString(); - - return { - postId: post.mid, - author: post.authordisplayname, - publishedTimestamp, - isBreakingNews, - isKeyEvent: Boolean(post.keytext), - title, - content - }; + return post; }; const dispatchLiveUpdateEvent = (eventType, data) => { @@ -40,9 +14,9 @@ const dispatchLiveUpdateEvent = (eventType, data) => { window.setTimeout(() => document.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0); }; -const listenToLiveBlogEvents = ({ liveBlogWrapperId, blogPath }) => { +const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogArticleUuid }) => { const invokeAction = (action, args) => { - const wrapper = document.querySelector(`[data-x-dash-id="${liveBlogWrapperId}"]`); + const wrapper = document.querySelector(`[data-x-dash-id="${liveBlogWrapperElementId}"]`); wrapper.dispatchEvent( new CustomEvent( 'x-interaction.trigger-action', @@ -51,11 +25,9 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperId, blogPath }) => { ); }; - // TODO: use next-live-events-api v2 routes - // const eventSource = new EventSource(`https://next-live-event.ft.com?eventid=${blogPath}&formatted=false`, { withCredentials: true }); - const eventSource = new EventSource(`http://localhost:3000/events`, { withCredentials: false }); + const eventSource = new EventSource(`https://next-live-event.ft.com/v2/liveblog/${liveBlogArticleUuid}`, { withCredentials: true }); - eventSource.addEventListener('msg', (event) => { + eventSource.addEventListener('insert-post', (event) => { const post = parsePost(event); if (!post) { @@ -66,7 +38,7 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperId, blogPath }) => { dispatchLiveUpdateEvent('LiveBlogWrapper.INSERT_POST', { post }); }); - eventSource.addEventListener('editmsg', (event) => { + eventSource.addEventListener('update-post', (event) => { const post = parsePost(event); if (!post) { @@ -77,7 +49,7 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperId, blogPath }) => { dispatchLiveUpdateEvent('LiveBlogWrapper.UPDATE_POST', { post }); }); - eventSource.addEventListener('delete', (event) => { + eventSource.addEventListener('delete-post', (event) => { const post = parsePost(event); if (!post) { From abcfe70303b7a3fb1752b7b96ec590a186a78b56 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Thu, 6 Aug 2020 12:01:43 +0100 Subject: [PATCH 542/760] Rename liveBlogArticleUuid to liveBlogPackageUuid --- components/x-live-blog-wrapper/src/LiveEventListener.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index b097f0e3b..baa250b1f 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -14,7 +14,7 @@ const dispatchLiveUpdateEvent = (eventType, data) => { window.setTimeout(() => document.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0); }; -const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogArticleUuid }) => { +const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid }) => { const invokeAction = (action, args) => { const wrapper = document.querySelector(`[data-x-dash-id="${liveBlogWrapperElementId}"]`); wrapper.dispatchEvent( @@ -25,7 +25,7 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogArticleUuid ); }; - const eventSource = new EventSource(`https://next-live-event.ft.com/v2/liveblog/${liveBlogArticleUuid}`, { withCredentials: true }); + const eventSource = new EventSource(`https://next-live-event.ft.com/v2/liveblog/${liveBlogPackageUuid}`, { withCredentials: true }); eventSource.addEventListener('insert-post', (event) => { const post = parsePost(event); From dcefa976fefa5498908071d78353ee2ea8c5aa52 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 19 Aug 2020 17:50:40 +0100 Subject: [PATCH 543/760] Remove HydratedLiveBlogWrapper To allow the client app to render hydration data we are removing the HydratedLiveBlogWrapper and exposing LiveBlogWrapper in this component. --- components/x-live-blog-wrapper/rollup.js | 2 +- .../src/HydratedLiveBlogWrapper.jsx | 17 ----------- .../src/LiveBlogWrapper.jsx | 30 ++++++++++++------- .../x-live-blog-wrapper/storybook/index.jsx | 4 +-- 4 files changed, 23 insertions(+), 30 deletions(-) delete mode 100644 components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx diff --git a/components/x-live-blog-wrapper/rollup.js b/components/x-live-blog-wrapper/rollup.js index c2ecbfaa0..1a89a8955 100644 --- a/components/x-live-blog-wrapper/rollup.js +++ b/components/x-live-blog-wrapper/rollup.js @@ -1,4 +1,4 @@ const xRollup = require('@financial-times/x-rollup'); const pkg = require('./package.json'); -xRollup({ input: './src/HydratedLiveBlogWrapper.jsx', pkg }); +xRollup({ input: './src/LiveBlogWrapper.jsx', pkg }); diff --git a/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx deleted file mode 100644 index 024f379a7..000000000 --- a/components/x-live-blog-wrapper/src/HydratedLiveBlogWrapper.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { h, Fragment } from '@financial-times/x-engine'; -import { LiveBlogWrapper } from "./LiveBlogWrapper"; -import { Serialiser, HydrationData } from '@financial-times/x-interaction'; -import { listenToLiveBlogEvents } from './LiveEventListener'; - -const HydratedLiveBlogWrapper = (props) => { - const serialiser = new Serialiser(); - - return ( - - - - - ) -}; - -export { HydratedLiveBlogWrapper, listenToLiveBlogEvents }; diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 5eb1cdeb7..36afe2d14 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -1,6 +1,7 @@ -import { h, Fragment } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine'; import { LiveBlogPost } from '@financial-times/x-live-blog-post'; import { withActions } from '@financial-times/x-interaction'; +import { listenToLiveBlogEvents } from './LiveEventListener'; const liveBlogWrapperActions = withActions({ insertPost (post) { @@ -29,13 +30,22 @@ const liveBlogWrapperActions = withActions({ } }); -const LiveBlogWrapper = liveBlogWrapperActions(({ posts = [], articleUrl, showShareButtons }) => { - const postElements = posts.map((post) => ); - return ( -
    - {postElements} -
    - ); -}); -export { LiveBlogWrapper }; +const LiveBlogWrapper = liveBlogWrapperActions( + function LiveBlogWrapper({ posts = [], articleUrl, showShareButtons }) { + const postElements = posts.map(post => + + ); + + return ( +
    + {postElements} +
    + ); + } +); + +export { LiveBlogWrapper, listenToLiveBlogEvents }; diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 2a994549d..0b2b5658a 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { withKnobs, text } from '@storybook/addon-knobs'; -import { HydratedLiveBlogWrapper } from '../src/HydratedLiveBlogWrapper'; +import { LiveBlogWrapper } from '../src/LiveBlogWrapper'; const defaultProps = { message: 'Test', @@ -50,5 +50,5 @@ storiesOf('x-live-blog-wrapper', module) message: toggleMessage(), }; - return ; + return ; }); From 0aa3a542a7b1353ee189263556a67261d6093242 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Fri, 21 Aug 2020 11:29:54 +0100 Subject: [PATCH 544/760] Remove unused function --- components/x-live-blog-wrapper/src/LiveEventListener.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index baa250b1f..f5a12d3e7 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -60,11 +60,6 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.postId }); }); - // TODO: do we handle live blog status updates in this component? - eventSource.addEventListener('close', (event) => { - // TODO - }); - }; export { listenToLiveBlogEvents }; From c23643a08a8c1705051fcf4a3dd507823d393820 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Fri, 21 Aug 2020 11:51:11 +0100 Subject: [PATCH 545/760] Rename function names to make them easier to read --- .../src/LiveBlogWrapper.jsx | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 36afe2d14..a3c754346 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -3,7 +3,7 @@ import { LiveBlogPost } from '@financial-times/x-live-blog-post'; import { withActions } from '@financial-times/x-interaction'; import { listenToLiveBlogEvents } from './LiveEventListener'; -const liveBlogWrapperActions = withActions({ +const withLiveBlogWrapperActions = withActions({ insertPost (post) { return ({ posts }) => { const updatedPosts = [ post, ...posts ]; @@ -30,22 +30,21 @@ const liveBlogWrapperActions = withActions({ } }); - -const LiveBlogWrapper = liveBlogWrapperActions( - function LiveBlogWrapper({ posts = [], articleUrl, showShareButtons }) { - const postElements = posts.map(post => - - ); - - return ( -
    - {postElements} -
    - ); - } -); +const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons }) => { + const postElements = posts.map(post => + + ); + + return ( +
    + {postElements} +
    + ); +} + +const LiveBlogWrapper = withLiveBlogWrapperActions(BaseLiveBlogWrapper); export { LiveBlogWrapper, listenToLiveBlogEvents }; From 90fd720c3b1f6916d2348ac237f3ba4a12f1216f Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Thu, 20 Aug 2020 15:17:04 +0100 Subject: [PATCH 546/760] Dispatch the event from wrapper element instead of document --- .../x-live-blog-wrapper/src/LiveEventListener.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index f5a12d3e7..4dc4cc12c 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -8,15 +8,11 @@ const parsePost = (event) => { return post; }; -const dispatchLiveUpdateEvent = (eventType, data) => { - // consumer app will need to consume this event after the component is rendered. therefore, - // we defer dispatching of this event. - window.setTimeout(() => document.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0); -}; const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid }) => { + const wrapper = document.querySelector(`[data-x-dash-id="${liveBlogWrapperElementId}"]`); + const invokeAction = (action, args) => { - const wrapper = document.querySelector(`[data-x-dash-id="${liveBlogWrapperElementId}"]`); wrapper.dispatchEvent( new CustomEvent( 'x-interaction.trigger-action', @@ -25,6 +21,12 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid ); }; + const dispatchLiveUpdateEvent = (eventType, data) => { + // consumer app will need to consume this event after the component is rendered. + // therefore, we defer dispatching of this event. + window.setTimeout(() => wrapper.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0); + }; + const eventSource = new EventSource(`https://next-live-event.ft.com/v2/liveblog/${liveBlogPackageUuid}`, { withCredentials: true }); eventSource.addEventListener('insert-post', (event) => { From 51c297f654ee717d52d01de228e2446def97ad51 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Fri, 21 Aug 2020 16:32:02 +0100 Subject: [PATCH 547/760] Explain usage of setTimeout(fn, 0) for delaying event dispatch --- .../src/LiveEventListener.js | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index 4dc4cc12c..f996e3d66 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -22,9 +22,35 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid }; const dispatchLiveUpdateEvent = (eventType, data) => { - // consumer app will need to consume this event after the component is rendered. - // therefore, we defer dispatching of this event. - window.setTimeout(() => wrapper.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0); + /* + We dispatch live update events to notify the consuming app about added / updated posts. + + Consuming app uses these events to execute tasks like initialising Origami components + on the updated elements. + + We want the rendering of the updates in the DOM to finish before dispatching this event, + because the consumer needs to reference the updated DOM elements. + + If we dispatch the event in the same event loop with DOM element updates, consumer app + will handle the event before the updates are complete. + + window.setTimeout(fn, 0) will defer the execution of the inner function until the + current event loop completes, which is enough time for the DOM updates to finish. + + More information can be found in MDN setTimeout documentation. Please refer to + "Late timeouts" heading in this page: + https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Late_timeouts + + > ... the timeout can also fire later when the page (or the OS/browser itself) is busy + > with other tasks. One important case to note is that the function or code snippet + > cannot be executed until the thread that called setTimeout() has terminated. + > ... + > This is because even though setTimeout was called with a delay of zero, it's placed on + > a queue and scheduled to run at the next opportunity; not immediately. + */ + window.setTimeout( + () => wrapper.dispatchEvent(new CustomEvent(eventType, { detail: data })), + 0); }; const eventSource = new EventSource(`https://next-live-event.ft.com/v2/liveblog/${liveBlogPackageUuid}`, { withCredentials: true }); From f69eef9cd811749414e9dcb2b8e856998167d8ac Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Mon, 24 Aug 2020 15:28:16 +0100 Subject: [PATCH 548/760] Modify posts array instead of creating a new array when updating the state This will improve performance and memory usage. --- .../src/LiveBlogWrapper.jsx | 21 +++++--- .../src/__tests__/LiveBlogWrapper.test.jsx | 50 +++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index a3c754346..bb61a386e 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -6,26 +6,31 @@ import { listenToLiveBlogEvents } from './LiveEventListener'; const withLiveBlogWrapperActions = withActions({ insertPost (post) { return ({ posts }) => { - const updatedPosts = [ post, ...posts ]; + posts.unshift(post) - return { posts: updatedPosts }; + return { posts }; }; }, updatePost (updated) { return ({ posts }) => { - const updatedPosts = posts.map( - post => post.postId === updated.postId ? updated : post - ); + const index = posts.findIndex(post => post.postId === updated.postId); + if (index >= 0) { + posts[index] = updated; + } - return { posts: updatedPosts }; + return { posts }; }; }, deletePost (postId) { return ({ posts }) => { - const updatedPosts = posts.filter((post) => post.postId !== postId); - return { posts: updatedPosts }; + const index = posts.findIndex(post => post.postId === postId); + if (index >= 0) { + posts.splice(index, 1); + } + + return { posts }; }; } }); diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 69fb87302..3eec34497 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -32,3 +32,53 @@ describe('x-live-blog-wrapper', () => { expect(liveBlogWrapper.html()).toContain('Post 2 Title'); }); }); + +describe('liveBlogWrapperActions', () => { + let posts; + let actions; + + beforeEach(() => { + posts = [ post1, post2 ]; + + // liveBlogActions are not exported from the module, but we can access them via + // the props of LiveBlogWrapper component. + const liveBlogWrapper = LiveBlogWrapper({}); + actions = liveBlogWrapper.props.actions; + }); + + it('inserts a new post to the top of the list', () => { + const post3 = { + postId: '3' + } + + // insertPost function returns another function that takes the list of component props + // as an argument and returns the updated props. + actions.insertPost(post3)({ posts }); + + expect(posts.length).toEqual(3); + expect(posts[0].postId).toEqual('3'); + }); + + it('updates a post', () => { + const updatedPost2 = { + postId: '2', + title: 'Updated title' + }; + + // updatePost function returns another function that takes the list of component props + // as an argument and returns the updated props. + actions.updatePost(updatedPost2)({ posts }); + + expect(posts.length).toEqual(2); + expect(posts[1].title).toEqual('Updated title'); + }); + + it('deletes a post', () => { + // deletePost function returns another function that takes the list of component props + // as an argument and returns the updated props. + actions.deletePost('1')({ posts }); + + expect(posts.length).toEqual(1); + expect(posts[0].postId).toEqual('2'); + }); +}); From afe407ecf8bf37f1e23d9d796c00a273a0bdd26d Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Tue, 25 Aug 2020 12:19:16 +0100 Subject: [PATCH 549/760] Refactor action function implementation to make them more clear We want to emphasize that the inner functions returned by action functions accept props as an argument and modify them in place. --- .../src/LiveBlogWrapper.jsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index bb61a386e..533868599 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -5,32 +5,32 @@ import { listenToLiveBlogEvents } from './LiveEventListener'; const withLiveBlogWrapperActions = withActions({ insertPost (post) { - return ({ posts }) => { - posts.unshift(post) + return (props) => { + props.posts.unshift(post); - return { posts }; + return props; }; }, updatePost (updated) { - return ({ posts }) => { - const index = posts.findIndex(post => post.postId === updated.postId); + return (props) => { + const index = props.posts.findIndex(post => post.postId === updated.postId); if (index >= 0) { - posts[index] = updated; + props.posts[index] = updated; } - return { posts }; + return props; }; }, deletePost (postId) { - return ({ posts }) => { - const index = posts.findIndex(post => post.postId === postId); + return (props) => { + const index = props.posts.findIndex(post => post.postId === postId); if (index >= 0) { - posts.splice(index, 1); + props.posts.splice(index, 1); } - return { posts }; + return props; }; } }); From 3573f1fda22472f611e930ef9e1bbf3affe36f17 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Tue, 25 Aug 2020 12:20:42 +0100 Subject: [PATCH 550/760] Add missing semicolon --- .../x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 3eec34497..7813a506e 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -49,7 +49,7 @@ describe('liveBlogWrapperActions', () => { it('inserts a new post to the top of the list', () => { const post3 = { postId: '3' - } + }; // insertPost function returns another function that takes the list of component props // as an argument and returns the updated props. From 78de6b2f2c9fda8d3b00f79916ed96cabd9a6d7d Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 26 Aug 2020 15:44:55 +0100 Subject: [PATCH 551/760] Update readme with usage instructions --- components/x-live-blog-wrapper/package.json | 2 +- components/x-live-blog-wrapper/readme.md | 102 ++++++++++++++++++-- 2 files changed, 94 insertions(+), 10 deletions(-) diff --git a/components/x-live-blog-wrapper/package.json b/components/x-live-blog-wrapper/package.json index be299abd2..e57c25f5f 100644 --- a/components/x-live-blog-wrapper/package.json +++ b/components/x-live-blog-wrapper/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-live-blog-wrapper", "engines": { - "node": ">= 6.0.0" + "node": ">= 10.x" }, "publishConfig": { "access": "public" diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index 4464ba1b1..1f5af3864 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -1,11 +1,11 @@ # x-liveblog-wrapper -This module has these features and scope. +This module displays a list of live blog posts using `x-live-blog-post` component. Also handles live updates on the live blog. ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is compatible with Node 10+ and is distributed on npm. ```bash npm install --save @financial-times/x-liveblog-wrapper @@ -22,22 +22,106 @@ The components provided by this module are all functions that expect a map of [p ```jsx import React from 'react'; -import { Live-blog-wrapper } from '@financial-times/x-live-blog-wrapper'; +import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; // A == B == C -const a = Live-blog-wrapper(props); -const b = ; -const c = React.createElement(Live-blog-wrapper, props); +const a = LiveBlogWrapper(props); +const b = ; +const c = React.createElement(LiveBlogWrapper, props); ``` All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. [jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ +### Server side rendering and hydrating +When rendering this component at the server side, hydration data must be rendered to the document using `Serialiser` and `HydrationData` components which are provided by `x-interaction`. + +To be able to successfully hydrate this component at the client side, `id` property **must** be provided when rendering it at the server side. `x-interaction` will add this id to the markup in `data-x-dash-id` property. This property can later be used to identify the markup. + +Consuming app needs to ensure that the `id` is unique. + +```jsx +import { Serialiser, HydrationData } from '@financial-times/x-interaction'; +import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; + +const serialiser = new Serialiser(); + + + +``` + +To hydrate this component at the client side, use `hydrate()` function provided by `x-interaction`. + +```js +import { hydrate } from '@financial-times/x-interaction'; + +hydrate(); +``` + +### Live updates +This component exports a function named `listenToLiveBlogEvents` which is used for listening to client side live blog updates. This function should be called after hydrating the component if it is rendered at the server side. +```js +import { hydrate } from '@financial-times/x-interaction'; +import { listenToLiveBlogEvents } from '@financial-times/x-live-blog-wrapper'; + +hydrate(); +listenToLiveBlogEvents(); +``` + +### Client side events +This component dispatches the following client side events to notify the consuming app about live updates. Consuming apps typically use these events to initialise Origami components on the newly rendered markup. +```jsx + +``` + +```js +... + +listenToLiveBlogEvents(); + +const wrapperElement = document.querySelector( + `[data-x-dash-id="x-dash-element-id"]` +); + +wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', + (ev) => { + const { post } = ev.detail; + + // post object contains data about a live blog post + // post.postId can be used to identify the newly rendered + // LiveBlogPost element + }); + +document.addEventListener('LiveBlogWrapper.DELETE_POST', + (ev) => { + const { postId } = ev.detail; + + // postId can be used to identify the deleted + // LiveBlogPost element + }); + +document.addEventListener('LiveBlogWrapper.UPDATE_POST', + (ev) => { + const { post } = ev.detail; + + // post object contains data about a live blog post + // post.postId can be used to identify the newly rendered + // LiveBlogPost element + }); +``` ### Properties Feature | Type | Notes -----------------|--------|---------------------------- -`propertyName1` | String | -`propertyName2` | String | -`propertyName2` | String | +`articleUrl` | String | URL of the live blog - used for sharing +`showShareButtons` | Boolean | if `true` displays social media sharing buttons in posts +`posts` | Array | Array of live blog post data From 6a88a514d0621834b78262bb51504207a499186b Mon Sep 17 00:00:00 2001 From: Tunca Bergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 26 Aug 2020 16:26:10 +0100 Subject: [PATCH 552/760] Update components/x-live-blog-wrapper/readme.md Co-authored-by: Glynn Phillips <524573+GlynnPhillips@users.noreply.github.com> --- components/x-live-blog-wrapper/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index 1f5af3864..074bdf787 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -39,7 +39,7 @@ When rendering this component at the server side, hydration data must be rendere To be able to successfully hydrate this component at the client side, `id` property **must** be provided when rendering it at the server side. `x-interaction` will add this id to the markup in `data-x-dash-id` property. This property can later be used to identify the markup. -Consuming app needs to ensure that the `id` is unique. +The consuming app needs to ensure that the `id` is unique. ```jsx import { Serialiser, HydrationData } from '@financial-times/x-interaction'; From d558462d5b014d603bf33775f4ee7a44529562a6 Mon Sep 17 00:00:00 2001 From: Tunca Bergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 26 Aug 2020 16:26:25 +0100 Subject: [PATCH 553/760] Update components/x-live-blog-wrapper/readme.md Co-authored-by: Glynn Phillips <524573+GlynnPhillips@users.noreply.github.com> --- components/x-live-blog-wrapper/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index 074bdf787..59f8a98c3 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -37,7 +37,7 @@ All `x-` components are designed to be compatible with a variety of runtimes, no ### Server side rendering and hydrating When rendering this component at the server side, hydration data must be rendered to the document using `Serialiser` and `HydrationData` components which are provided by `x-interaction`. -To be able to successfully hydrate this component at the client side, `id` property **must** be provided when rendering it at the server side. `x-interaction` will add this id to the markup in `data-x-dash-id` property. This property can later be used to identify the markup. +To successfully hydrate this component at the client side, the `id` property **must** be provided when rendering it at the server side. `x-interaction` will add this id to the markup as a `data-x-dash-id` attribute. This property can later be used to identify the markup. The consuming app needs to ensure that the `id` is unique. From a80baecacbc8777f0a809a9b83d90f5329c14a1d Mon Sep 17 00:00:00 2001 From: Tunca Bergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 26 Aug 2020 16:26:37 +0100 Subject: [PATCH 554/760] Update components/x-live-blog-wrapper/readme.md Co-authored-by: Glynn Phillips <524573+GlynnPhillips@users.noreply.github.com> --- components/x-live-blog-wrapper/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index 59f8a98c3..c64978a56 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -1,6 +1,6 @@ # x-liveblog-wrapper -This module displays a list of live blog posts using `x-live-blog-post` component. Also handles live updates on the live blog. +This module displays a list of live blog posts using `x-live-blog-post` component. It also connects to an event stream which provides updates for the list. Based on these update events this component will add, remove and update `x-live-blog-post` components in the list. ## Installation From 8714dd1cae82ec1079f8a84a44a469cc6abb38a7 Mon Sep 17 00:00:00 2001 From: Tunca Bergmen <5130615+tbergmen@users.noreply.github.com> Date: Thu, 27 Aug 2020 09:41:51 +0100 Subject: [PATCH 555/760] Update components/x-live-blog-wrapper/readme.md Co-authored-by: Keran Braich <30316203+ker-an@users.noreply.github.com> --- components/x-live-blog-wrapper/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index c64978a56..3ad51f580 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -1,4 +1,4 @@ -# x-liveblog-wrapper +# x-live-blog-wrapper This module displays a list of live blog posts using `x-live-blog-post` component. It also connects to an event stream which provides updates for the list. Based on these update events this component will add, remove and update `x-live-blog-post` components in the list. From 58cb15c5b0b5d91ae13378f6a47345ea128beb4c Mon Sep 17 00:00:00 2001 From: Tunca Bergmen <5130615+tbergmen@users.noreply.github.com> Date: Thu, 27 Aug 2020 09:42:04 +0100 Subject: [PATCH 556/760] Update components/x-live-blog-wrapper/readme.md Co-authored-by: Keran Braich <30316203+ker-an@users.noreply.github.com> --- components/x-live-blog-wrapper/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index 3ad51f580..2ea5257d7 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -8,7 +8,7 @@ This module displays a list of live blog posts using `x-live-blog-post` componen This module is compatible with Node 10+ and is distributed on npm. ```bash -npm install --save @financial-times/x-liveblog-wrapper +npm install --save @financial-times/x-live-blog-wrapper ``` The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. From 6ef663758e4adc68f1d8c258237147af2a5cc316 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 2 Sep 2020 17:32:35 +0100 Subject: [PATCH 557/760] Updates to use this component at the client side There are two major updates to use this component at the client side: We need to render a property named `data-x-live-blog-wrapper-id` in the main element. This is now used to identify the element at the client side for live updates. Previously we used `data-x-dash-id` for this purpose, but that property is only rendered when using this component with server side rendering. Add `actions` argument to `listenToLiveBlogEvents` function. With server side rendering and hydrating at the client side, component actions can be triggered by dispatching a custom event named `x-interaction.trigger-actions`. This method can't be used with client side rendering. Therefore we need pass the actions object to this function as an argument. Actions object can be gathered by passing a callback function to the component in a property named actionsRef. For more information, see https://github.com/Financial-Times/x-dash/tree/master/components/x-interaction#triggering-actions-externally Usage of this function is now documented in more detail in the readme file. --- components/x-live-blog-wrapper/readme.md | 69 ++++++++++++++++++- .../src/LiveBlogWrapper.jsx | 4 +- .../src/LiveEventListener.js | 36 +++++++--- 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index 2ea5257d7..cda974335 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -34,6 +34,24 @@ All `x-` components are designed to be compatible with a variety of runtimes, no [jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ +### Client side rendering +This component can be used at the client side. To access the actions, a function needs to be passed into the actionsRef property of the LiveBlogWrapper element. + +```jsx +import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; + +const actionsRef = actions => { + // Use actions to insert, update and delete live blog posts +}; + + +``` + ### Server side rendering and hydrating When rendering this component at the server side, hydration data must be rendered to the document using `Serialiser` and `HydrationData` components which are provided by `x-interaction`. @@ -64,13 +82,59 @@ hydrate(); ``` ### Live updates -This component exports a function named `listenToLiveBlogEvents` which is used for listening to client side live blog updates. This function should be called after hydrating the component if it is rendered at the server side. +This component exports a function named `listenToLiveBlogEvents` which is used for listening to client side live blog updates. These updates come in the form of server sent events sent by `next-live-event-api`. + +This function is used in slightly different ways when rendering the component at the client side vs rendering it at the server side. + +#### Client side rendering +A reference to the actions object should be passed as an argument when calling this function for a client side rendered component. +```jsx +import { LiveBlogWrapper, listenToLiveBlogEvents } from '@financial-times/x-live-blog-wrapper'; + +const actionsRef = actions => { + listenToLiveBlogEvents({ + liveBlogWrapperElementId: 'live-blog-wrapper', + liveBlogPackageUuid: 'package-uuid', + actions // for client side rendered component only + }); +}; + + +``` + +#### Server side rendering +This function should be called after hydrating the component if it is rendered at the server side. + +Server side: +```jsx +import { Serialiser, HydrationData } from '@financial-times/x-interaction'; +import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; + +const serialiser = new Serialiser(); + + + +``` + +Client side: ```js import { hydrate } from '@financial-times/x-interaction'; import { listenToLiveBlogEvents } from '@financial-times/x-live-blog-wrapper'; hydrate(); -listenToLiveBlogEvents(); +listenToLiveBlogEvents({ + liveBlogWrapperElementId: 'live-blog-wrapper', + liveBlogPackageUuid: 'package-uuid' +}); ``` ### Client side events @@ -125,3 +189,4 @@ Feature | Type | Notes `articleUrl` | String | URL of the live blog - used for sharing `showShareButtons` | Boolean | if `true` displays social media sharing buttons in posts `posts` | Array | Array of live blog post data +`id` | String | **(required)** Unique id used for identifying the element in the document. diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 533868599..4d3962abf 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -35,7 +35,7 @@ const withLiveBlogWrapperActions = withActions({ } }); -const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons }) => { +const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id }) => { const postElements = posts.map(post => { ); return ( -
    +
    {postElements}
    ); diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index f996e3d66..617992269 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -9,16 +9,36 @@ const parsePost = (event) => { }; -const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid }) => { - const wrapper = document.querySelector(`[data-x-dash-id="${liveBlogWrapperElementId}"]`); +const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, actions }) => { + const wrapper = document.querySelector(`[data-live-blog-wrapper-id="${liveBlogWrapperElementId}"]`); const invokeAction = (action, args) => { - wrapper.dispatchEvent( - new CustomEvent( - 'x-interaction.trigger-action', - { detail: { action, args } } - ) - ); + if (actions) { + // When the component is rendered at the client side, we get a reference to the + // actions via setting the actionsRef property. + // + // In that case 'actions' argument should be passed when calling the + // listenToLiveBlogEvents function. We use those actions directly when that + // argument is defined. + // + // For more information: + // https://github.com/Financial-Times/x-dash/tree/master/components/x-interaction#triggering-actions-externally + actions[action](...args); + } else { + // When the component is rendered at the server side, we don't have a reference to + // the actions object. HydrationWrapper in x-interaction listens to this specific + // event and triggers the action supplied in the event detail. + // + // If no 'actions' argument is passed when calling listenToLiveBlogEvents + // function, we assume the component is rendered at the server side and trigger + // the actions using this method. + wrapper.dispatchEvent( + new CustomEvent( + 'x-interaction.trigger-action', + { detail: { action, args } } + ) + ); + } }; const dispatchLiveUpdateEvent = (eventType, data) => { From 90a427fc993e3a5763419a63a5ba5c1694b29d2e Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Fri, 4 Sep 2020 13:03:36 +0100 Subject: [PATCH 558/760] Update invalid information in the readme document --- components/x-live-blog-wrapper/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index cda974335..ebad8ebe0 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -153,7 +153,7 @@ This component dispatches the following client side events to notify the consumi listenToLiveBlogEvents(); const wrapperElement = document.querySelector( - `[data-x-dash-id="x-dash-element-id"]` + `[data-live-blog-wrapper-id="x-dash-element-id"]` ); wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', @@ -165,7 +165,7 @@ wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', // LiveBlogPost element }); -document.addEventListener('LiveBlogWrapper.DELETE_POST', +wrapperElement.addEventListener('LiveBlogWrapper.DELETE_POST', (ev) => { const { postId } = ev.detail; @@ -173,7 +173,7 @@ document.addEventListener('LiveBlogWrapper.DELETE_POST', // LiveBlogPost element }); -document.addEventListener('LiveBlogWrapper.UPDATE_POST', +wrapperElement.addEventListener('LiveBlogWrapper.UPDATE_POST', (ev) => { const { post } = ev.detail; From e43a720e911f76dda3d14c463fc94c3f70198b3f Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 16 Sep 2020 17:01:50 +0100 Subject: [PATCH 559/760] Update live blog post property names in the live-blog-wrapper implementation --- .../src/LiveBlogWrapper.jsx | 6 ++--- .../src/LiveEventListener.js | 6 ++--- .../src/__tests__/LiveBlogWrapper.test.jsx | 22 ++++++++++--------- .../x-live-blog-wrapper/storybook/index.jsx | 18 +++++++-------- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 4d3962abf..908261ad3 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -14,7 +14,7 @@ const withLiveBlogWrapperActions = withActions({ updatePost (updated) { return (props) => { - const index = props.posts.findIndex(post => post.postId === updated.postId); + const index = props.posts.findIndex(post => post.id === updated.id); if (index >= 0) { props.posts[index] = updated; } @@ -25,7 +25,7 @@ const withLiveBlogWrapperActions = withActions({ deletePost (postId) { return (props) => { - const index = props.posts.findIndex(post => post.postId === postId); + const index = props.posts.findIndex(post => post.id === postId); if (index >= 0) { props.posts.splice(index, 1); } @@ -37,7 +37,7 @@ const withLiveBlogWrapperActions = withActions({ const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id }) => { const postElements = posts.map(post => - diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index 617992269..e5d84b8b8 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -1,7 +1,7 @@ const parsePost = (event) => { const post = JSON.parse(event.data); - if (!post || !post.postId) { + if (!post || !post.id) { return; } @@ -104,8 +104,8 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, return; } - invokeAction('deletePost', [ post.postId ]) - dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.postId }); + invokeAction('deletePost', [ post.id ]) + dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.id }); }); }; diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 7813a506e..eeb06d24c 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -4,20 +4,20 @@ const { mount } = require('@financial-times/x-test-utils/enzyme'); import { LiveBlogWrapper } from '../LiveBlogWrapper'; const post1 = { - postId: '1', + id: '1', title: 'Post 1 Title', - content: '

    Post 1

    ', - publishedTimestamp: new Date().toISOString(), + bodyHTML: '

    Post 1 body

    ', + publishedDate: new Date().toISOString(), isBreakingNews: true, articleUrl: 'Https://www.ft.com', showShareButtons: true }; const post2 = { - postId: '2', + id: '2', title: 'Post 2 Title', - content: '

    Post 2>

    ', - publishedTimestamp: new Date().toISOString(), + bodyHTML: '

    Post 2 body

    ', + publishedDate: new Date().toISOString(), isBreakingNews: false, articleUrl: 'Https://www.ft.com', showShareButtons: true @@ -29,7 +29,9 @@ describe('x-live-blog-wrapper', () => { const liveBlogWrapper = mount(); expect(liveBlogWrapper.html()).toContain('Post 1 Title'); + expect(liveBlogWrapper.html()).toContain('Post 1 body'); expect(liveBlogWrapper.html()).toContain('Post 2 Title'); + expect(liveBlogWrapper.html()).toContain('Post 2 body'); }); }); @@ -48,7 +50,7 @@ describe('liveBlogWrapperActions', () => { it('inserts a new post to the top of the list', () => { const post3 = { - postId: '3' + id: '3' }; // insertPost function returns another function that takes the list of component props @@ -56,12 +58,12 @@ describe('liveBlogWrapperActions', () => { actions.insertPost(post3)({ posts }); expect(posts.length).toEqual(3); - expect(posts[0].postId).toEqual('3'); + expect(posts[0].id).toEqual('3'); }); it('updates a post', () => { const updatedPost2 = { - postId: '2', + id: '2', title: 'Updated title' }; @@ -79,6 +81,6 @@ describe('liveBlogWrapperActions', () => { actions.deletePost('1')({ posts }); expect(posts.length).toEqual(1); - expect(posts[0].postId).toEqual('2'); + expect(posts[0].id).toEqual('2'); }); }); diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 0b2b5658a..4e4e085b9 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -7,29 +7,29 @@ const defaultProps = { message: 'Test', posts: [ { - postId: 12345, + id: 12345, title: 'Title 1', - content: '

    Post 1

    ', + bodyHTML: '

    Post 1

    ', isBreakingNews: false, - publishedTimestamp: '2020-05-13T18:52:28.000Z', + publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true, }, { - postId: 12346, + id: 12346, title: 'Title 2', - content: '

    Post 2

    ', + bodyHTML: '

    Post 2

    ', isBreakingNews: true, - publishedTimestamp: '2020-05-13T19:52:28.000Z', + publishedDate: '2020-05-13T19:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true, }, { - postId: 12347, + id: 12347, title: 'Title 3', - content: '

    Post 3

    ', + bodyHTML: '

    Post 3

    ', isBreakingNews: false, - publishedTimestamp: '2020-05-13T20:52:28.000Z', + publishedDate: '2020-05-13T20:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true, } From 9a1bf435324b3b3624c8267fea6a5fc38b0880d9 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 16 Sep 2020 17:11:43 +0100 Subject: [PATCH 560/760] Use id instead of postId if exists in live-blog-post --- components/x-live-blog-post/src/LiveBlogPost.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index afaf25280..327630060 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -20,7 +20,7 @@ const LiveBlogPost = (props) => { return (
    From 9660acb1421c48626af92d2546126ca16879c076 Mon Sep 17 00:00:00 2001 From: notlee Date: Fri, 18 Sep 2020 11:19:59 +0100 Subject: [PATCH 561/760] Correct promoted teaser markup. "by" should be included within the markup before the `o-teaser__promoted-by` element. This will require projects update `n-native-ads` to avoid a duplicate "by" https://github.com/Financial-Times/n-native-ads/pull/102 --- .../__snapshots__/snapshots.test.js.snap | 9 + components/x-teaser/package-lock.json | 6223 +++++++++++++++++ components/x-teaser/src/Promoted.jsx | 2 +- 3 files changed, 6233 insertions(+), 1 deletion(-) create mode 100644 components/x-teaser/package-lock.json diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index abb302c61..384c20ba9 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -387,6 +387,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` > Paid post + by @@ -869,6 +870,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with promoted data 1`] > Paid post + by @@ -1390,6 +1392,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1` > Paid post + by @@ -1800,6 +1803,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with promoted data 1`] > Paid post + by @@ -2413,6 +2417,7 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = ` > Paid post + by @@ -2859,6 +2864,7 @@ exports[`x-teaser / snapshots renders a Small teaser with promoted data 1`] = ` > Paid post + by @@ -3416,6 +3422,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`] > Paid post + by @@ -3934,6 +3941,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with promoted data 1`] = > Paid post + by @@ -4564,6 +4572,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d > Paid post + by diff --git a/components/x-teaser/package-lock.json b/components/x-teaser/package-lock.json new file mode 100644 index 000000000..2880e9b32 --- /dev/null +++ b/components/x-teaser/package-lock.json @@ -0,0 +1,6223 @@ +{ + "name": "@financial-times/x-teaser", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@financial-times/x-engine": { + "version": "file:../../packages/x-engine", + "requires": { + "assign-deep": "^1.0.0" + }, + "dependencies": { + "assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "requires": { + "assign-symbols": "^2.0.2" + } + }, + "assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" + } + } + }, + "@financial-times/x-rollup": { + "version": "file:../../packages/x-rollup", + "dev": true, + "requires": { + "@babel/core": "^7.6.4", + "@babel/plugin-external-helpers": "^7.2.0", + "@financial-times/x-babel-config": "file:../../packages/x-babel-config", + "chalk": "^2.4.2", + "log-symbols": "^3.0.0", + "rollup": "^1.23.0", + "rollup-plugin-babel": "^4.3.2", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-postcss": "^2.0.2" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/core": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.6", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/plugin-external-helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.10.4.tgz", + "integrity": "sha512-5mASqSthmRNYVXOphYzlqmR3Y8yp5SZMZhtKDh2DGV3R2PWGLEmP7qOahw66//6m4hjhlpV1bVM7xIJHt1F77Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@financial-times/x-babel-config": { + "version": "file:../../packages/x-babel-config", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.3.0", + "@babel/preset-env": "^7.4.3", + "babel-jest": "^24.0.0", + "fast-async": "^7.0.6" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/compat-data": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", + "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "semver": "^5.5.0" + } + }, + "@babel/core": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.6", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", + "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-react-jsx-experimental": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.11.5.tgz", + "integrity": "sha512-Vc4aPJnRZKWfzeCBsqTBnzulVNjABVdahSPhtdMD3Vs80ykx4a87jTHtF/VR+alSrDmNvat7l13yrRHauGcHVw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/types": "^7.11.5" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.10.4", + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-define-map": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", + "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", + "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", + "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", + "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", + "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.10.4" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz", + "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", + "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz", + "integrity": "sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A==", + "dev": true, + "requires": { + "@babel/helper-builder-react-jsx": "^7.10.4", + "@babel/helper-builder-react-jsx-experimental": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/preset-env": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", + "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.11.0", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-export-namespace-from": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.11.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.11.0", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.11.0", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.11.5", + "browserslist": "^4.12.0", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", + "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "dev": true, + "requires": { + "@jest/source-map": "^24.9.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/fake-timers": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", + "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0" + } + }, + "@jest/source-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", + "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/test-result": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", + "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "dev": true, + "requires": { + "@jest/console": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/transform": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", + "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.9.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.9.0", + "jest-regex-util": "^24.9.0", + "jest-util": "^24.9.0", + "micromatch": "^3.1.10", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/babel__core": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.14.tgz", + "integrity": "sha512-8w9szzKs14ZtBVuP6Wn7nMLRJ0D6dfB0VEBEyRgxrZ/Ln49aNMykrghM2FaNn4FJRzNppCSa0Rv9pBRM5Xc3wg==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/yargs": { + "version": "13.0.10", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.10.tgz", + "integrity": "sha512-MU10TSgzNABgdzKvQVW1nuuT+sgBMWeXNc3XOs5YXV5SDAK+PPja2eUuBNB9iqElu03xyEDqlnGw0jgl4nbqGQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "dev": true + }, + "ansi-styles": { + "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.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "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=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "babel-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", + "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "dev": true, + "requires": { + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.9.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", + "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", + "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "dev": true, + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", + "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "dev": true, + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.9.0" + } + }, + "babylon": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "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.0" + } + }, + "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.0" + } + }, + "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.0" + } + }, + "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" + } + } + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "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.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.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.0" + } + } + } + }, + "browserslist": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.3.tgz", + "integrity": "sha512-GcZPC5+YqyPO4SFnz48/B0YaCwS47Q9iPChRGi6t7HhflKBcINzFrJvRfC+jp30sRMKxF+d4EHGs27Z0XP1NaQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001131", + "electron-to-chromium": "^1.3.570", + "escalade": "^3.1.0", + "node-releases": "^1.1.61" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001131", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001131.tgz", + "integrity": "sha512-4QYi6Mal4MMfQMSqGIRPGbKIbZygeN83QsWq1ixpUwvtfgAZot5BrCKzGygvZaV+CnELdTwD0S4cqUNozq7/Cw==", + "dev": true + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "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.0" + } + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js-compat": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", + "dev": true, + "requires": { + "browserslist": "^4.8.5", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "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.2.9" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "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.0" + } + }, + "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.0" + } + }, + "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" + } + } + } + }, + "electron-to-chromium": { + "version": "1.3.570", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz", + "integrity": "sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", + "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "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.0" + } + }, + "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.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "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.0" + } + }, + "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.0" + } + }, + "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.0" + } + }, + "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.0" + } + }, + "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" + } + } + } + }, + "fast-async": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/fast-async/-/fast-async-7.0.6.tgz", + "integrity": "sha512-/iUa3eSQC+Xh5tN6QcVLsEsN7b1DaPIoTZo++VpLLIxtdNW2tEmMZex4TcrMeRnBwMOpZwue2CB171wjt5Kgqg==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0-beta.44", + "@babel/helper-module-imports": "^7.0.0-beta.44", + "babylon": "^7.0.0-beta.44", + "nodent-runtime": "^3.2.1", + "nodent-transform": "^3.2.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "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.0" + }, + "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.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "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" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "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.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "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.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.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "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.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.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "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.0.0" + }, + "dependencies": { + "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 + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, + "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.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.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "jest-haste-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", + "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.9.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", + "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0" + } + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "dev": true + }, + "jest-serializer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", + "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", + "dev": true + }, + "jest-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", + "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "dev": true, + "requires": { + "@jest/console": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/source-map": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "dev": true, + "requires": { + "leven": "^3.1.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "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.1", + "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.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-releases": { + "version": "1.1.61", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", + "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==", + "dev": true + }, + "nodent-runtime": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/nodent-runtime/-/nodent-runtime-3.2.1.tgz", + "integrity": "sha512-7Ws63oC+215smeKJQCxzrK21VFVlCFBkwl0MOObt0HOpVQXs3u483sAmtkF33nNqZ5rSOQjB76fgyPBmAUrtCA==", + "dev": true + }, + "nodent-transform": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/nodent-transform/-/nodent-transform-3.2.9.tgz", + "integrity": "sha512-4a5FH4WLi+daH/CGD5o/JWRR8W5tlCkd3nrDSkxbOzscJTyTUITltvOJeQjg3HJ1YgEuNyiPhQbvbtRjkQBByQ==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "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.0" + } + }, + "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.5" + } + } + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "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" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "dev": true, + "requires": { + "util.promisify": "^1.0.0" + } + }, + "regenerate": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.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.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "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.0" + } + }, + "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.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "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.0" + } + }, + "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.0" + } + }, + "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.0" + } + }, + "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" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "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.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "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.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "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.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "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" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/estree": { + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", + "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==", + "dev": true + }, + "@types/node": { + "version": "14.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.1.tgz", + "integrity": "sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw==", + "dev": true + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "dev": true + }, + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "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.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "browserslist": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.3.tgz", + "integrity": "sha512-GcZPC5+YqyPO4SFnz48/B0YaCwS47Q9iPChRGi6t7HhflKBcINzFrJvRfC+jp30sRMKxF+d4EHGs27Z0XP1NaQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001131", + "electron-to-chromium": "^1.3.570", + "escalade": "^3.1.0", + "node-releases": "^1.1.61" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001131", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001131.tgz", + "integrity": "sha512-4QYi6Mal4MMfQMSqGIRPGbKIbZygeN83QsWq1ixpUwvtfgAZot5BrCKzGygvZaV+CnELdTwD0S4cqUNozq7/Cw==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-modules-loader-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", + "integrity": "sha1-WQhmgpShvs0mGuCkziGwtVHyHRY=", + "dev": true, + "requires": { + "icss-replace-symbols": "1.1.0", + "postcss": "6.0.1", + "postcss-modules-extract-imports": "1.1.0", + "postcss-modules-local-by-default": "1.2.0", + "postcss-modules-scope": "1.1.0", + "postcss-modules-values": "1.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz", + "integrity": "sha1-AA29H47vIXqjaLmiEsX8QLKo8/I=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-selector-tokenizer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", + "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2" + } + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-what": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", + "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "dev": true, + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", + "dev": true + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true + }, + "csso": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", + "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.39" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", + "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", + "dev": true, + "requires": { + "mdn-data": "2.0.6", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", + "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "electron-to-chromium": { + "version": "1.3.570", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz", + "integrity": "sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "generic-names": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz", + "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0" + } + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", + "dev": true + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "dev": true + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "dev": true, + "requires": { + "import-from": "^3.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-callable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", + "dev": true + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-releases": { + "version": "1.1.61", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", + "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==", + "dev": true + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", + "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-queue": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.1.tgz", + "integrity": "sha512-miQiSxLYPYBxGkrldecZC18OTLjdUqnlRebGzPRiVxB8mco7usCmm7hFuxiTvp93K18JnLtE4KMMycjAu/cQQg==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.1.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "dev": true + }, + "postcss": { + "version": "7.0.34", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.34.tgz", + "integrity": "sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-calc": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.4.tgz", + "integrity": "sha512-0I79VRAd1UTkaHzY9w83P39YGO/M3bG7/tNLrHGEunBolfoGM0hSjrGvjoeaj0JE/zIw5GsI2KZ0UwDJqv5hjw==", + "dev": true, + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.1.tgz", + "integrity": "sha512-D2ENobdoZsW0+BHy4x1CAkXtbXtYWYRIxL/JbtRBqrRGOPtJ2zoga/bEZWhV/ShWB5saVxJMzbMdSyA/vv4tXw==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "dependencies": { + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "dev": true, + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-2.0.0.tgz", + "integrity": "sha512-eqp+Bva+U2cwQO7dECJ8/V+X+uH1HduNeITB0CPPFAu6d/8LKQ32/j+p9rQ2YL1QytVcrNU0X+fBqgGmQIA1Rw==", + "dev": true, + "requires": { + "css-modules-loader-core": "^1.1.0", + "generic-names": "^2.0.1", + "lodash.camelcase": "^4.3.0", + "postcss": "^7.0.1", + "string-hash": "^1.1.1" + } + }, + "postcss-modules-extract-imports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", + "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "dev": true, + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, + "promise.series": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", + "integrity": "sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", + "dev": true + }, + "rollup": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" + } + }, + "rollup-plugin-babel": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", + "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-postcss": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-2.9.0.tgz", + "integrity": "sha512-Y7qDwlqjZMBexbB1kRJf+jKIQL8HR6C+ay53YzN+nNJ64hn1PNZfBE3c61hFUhD//zrMwmm7uBW30RuTi+CD0w==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "concat-with-sourcemaps": "^1.1.0", + "cssnano": "^4.1.10", + "import-cwd": "^3.0.0", + "p-queue": "^6.3.0", + "pify": "^5.0.0", + "postcss": "^7.0.27", + "postcss-load-config": "^2.1.0", + "postcss-modules": "^2.0.0", + "promise.series": "^0.2.0", + "resolve": "^1.16.0", + "rollup-pluginutils": "^2.8.2", + "safe-identifier": "^0.4.1", + "style-inject": "^0.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-identifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", + "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=", + "dev": true + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "style-inject": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", + "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==", + "dev": true + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", + "dev": true + } + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" + } + } +} diff --git a/components/x-teaser/src/Promoted.jsx b/components/x-teaser/src/Promoted.jsx index a7c4dd205..8adaa09c3 100644 --- a/components/x-teaser/src/Promoted.jsx +++ b/components/x-teaser/src/Promoted.jsx @@ -2,7 +2,7 @@ import { h } from '@financial-times/x-engine'; export default ({ promotedPrefixText, promotedSuffixText }) => (
    - {promotedPrefixText} + {promotedPrefixText} by {` ${promotedSuffixText} `}
    ); From f799c6738a2094aa2deb35b301d5e1251d208287 Mon Sep 17 00:00:00 2001 From: notlee Date: Fri, 18 Sep 2020 12:04:11 +0100 Subject: [PATCH 562/760] Remove duplicate "by" in x-teaser promoted content tests --- components/x-teaser/__fixtures__/promoted.json | 2 +- .../__snapshots__/snapshots.test.js.snap | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/x-teaser/__fixtures__/promoted.json b/components/x-teaser/__fixtures__/promoted.json index aa95056a9..3d1bb68ec 100644 --- a/components/x-teaser/__fixtures__/promoted.json +++ b/components/x-teaser/__fixtures__/promoted.json @@ -5,7 +5,7 @@ "title": "Why eSports companies are on a winning streak", "standfirst": "ESports is big business and about to get bigger: global revenues could hit $1.5bn by 2020", "promotedPrefixText": "Paid post", - "promotedSuffixText": "by UBS", + "promotedSuffixText": "UBS", "image": { "url": "https://tpc.googlesyndication.com/pagead/imgad?id=CICAgKCrm_3yahABGAEyCMx3RoLss603", "width": 700, diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 384c20ba9..766cb736b 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -391,7 +391,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` - by UBS + UBS
    - by UBS + UBS
    - by UBS + UBS
    - by UBS + UBS
    - by UBS + UBS
    - by UBS + UBS
    - by UBS + UBS
    - by UBS + UBS
    - by UBS + UBS
    Date: Fri, 18 Sep 2020 16:54:53 +0100 Subject: [PATCH 563/760] Removes snyk monitor task from CircleCI publish step This task currently fails for an unknown reason. We have created a support ticket in snyk support and waiting for a response. Meanwhile, to unblock release of components, we remove this task from the publish step temporarily. --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e118f4e35..210f624f9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -120,7 +120,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From 1774c4b12dafeae98ce15697531ed65f49a276b5 Mon Sep 17 00:00:00 2001 From: andygout Date: Mon, 21 Sep 2020 09:59:20 +0100 Subject: [PATCH 564/760] Upgrade jest-enzyme dependency ^6.0.4 -> ^7.0.2 --- packages/x-test-utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-test-utils/package.json b/packages/x-test-utils/package.json index 193cd74db..2aac2c177 100644 --- a/packages/x-test-utils/package.json +++ b/packages/x-test-utils/package.json @@ -13,7 +13,7 @@ "dependencies": { "enzyme": "^3.6.0", "enzyme-adapter-react-16": "^1.5.0", - "jest-enzyme": "^6.0.4", + "jest-enzyme": "^7.1.2", "react": "^16.5.0", "react-dom": "^16.5.0" } From bfcc797ddae640d94af5295095523b4903af671d Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Mon, 21 Sep 2020 11:52:38 +0100 Subject: [PATCH 565/760] Fix live-blog-post padding The bottom padding for the live blog post used to rely on sharing buttons to be visible. In the ft-app we don't display the share buttons in this component and the padding doesn't look right. This fixes the bottom padding regardless of displaying or hiding the share buttons in that component. --- components/x-live-blog-post/src/LiveBlogPost.scss | 2 +- components/x-live-blog-wrapper/storybook/index.jsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index cf5b5de51..427686126 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -7,6 +7,7 @@ border-bottom: 1px solid oColorsMix(black, paper, 20); margin-top: oSpacingByName('s8'); color: oColorsMix(black, paper, 90); + padding-bottom: oSpacingByName('s8'); } .live-blog-post__title { @@ -49,7 +50,6 @@ .live-blog-post__share-buttons { margin-top: oSpacingByName('s6'); - margin-bottom: oSpacingByName('s8'); } .live-blog-post__breaking-news { diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 4e4e085b9..9b639afd5 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { withKnobs, text } from '@storybook/addon-knobs'; import { LiveBlogWrapper } from '../src/LiveBlogWrapper'; +import '../../x-live-blog-post/dist/LiveBlogPost.css'; const defaultProps = { message: 'Test', From 0977684c427639ff0cdc5d7656b1db5289e88b81 Mon Sep 17 00:00:00 2001 From: Anton Samper Rivaya Date: Mon, 28 Sep 2020 09:41:39 +0100 Subject: [PATCH 566/760] Revert "Removes snyk monitor task from CircleCI publish step" This reverts commit 71b5bd6e480abe5210939262dc38461a963354a3. --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 210f624f9..e118f4e35 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -120,6 +120,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From 01ccc80b575ffaa0ddd2c66cd39961372289a01d Mon Sep 17 00:00:00 2001 From: John Kavanagh Date: Thu, 1 Oct 2020 09:44:49 +0100 Subject: [PATCH 567/760] set highest quality for xxl images with imageHighestQuality = true --- components/x-teaser/src/Image.jsx | 5 +++-- components/x-teaser/src/concerns/image-service.js | 13 +++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 99075207e..bb0820d16 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -26,10 +26,11 @@ const LazyImage = ({ src, lazyLoad }) => { return ; }; -export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, ...props }) => { +export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, imageHighestQuality, ...props }) => { const displayUrl = relativeUrl || url; const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')); - const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize]) : image.url; + const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {}; + const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize], options) : image.url; const ImageComponent = imageLazyLoad ? LazyImage : NormalImage; return image ? ( diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index 855b10786..25132f75b 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -1,5 +1,9 @@ const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw'; -const OPTIONS = ['source=next', 'fit=scale-down', 'dpr=2']; +const OPTIONS = Object.freeze({ + source: 'next', + fit: 'scale-down', + dpr: 2, +}); /** * Image Service @@ -8,7 +12,8 @@ const OPTIONS = ['source=next', 'fit=scale-down', 'dpr=2']; * @param {String} options */ export default function imageService(url, width, options) { - const encoded = encodeURIComponent(url); - const href = `${BASE_URL}/${encoded}?${OPTIONS.join('&')}&width=${width}`; - return options ? href + '&' + options : href; + const imageSrc = new URL(`${BASE_URL}/${encodeURIComponent(url)}`); + imageSrc.search = new URLSearchParams({...OPTIONS, ...options }); + imageSrc.searchParams.set('width', width); + return imageSrc.href; } From 7675e04d906133287f3d7dd3d056bf791031bd4e Mon Sep 17 00:00:00 2001 From: John Kavanagh Date: Thu, 1 Oct 2020 22:39:03 +0100 Subject: [PATCH 568/760] update the docs --- components/x-teaser/readme.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index 7a187643e..6d7b76fae 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -167,11 +167,13 @@ Property | Type | Notes #### Image Props -Property | Type | Notes -----------------|-----------------------|-------------------------------- -`image` | [media](#media-props) | -`imageSize` | String | XS, Small, Medium, Large, XL or XXL -`imageLazyLoad` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. +Property | Type | Notes +---------------------|-----------------------|-------------------------------- +`image` | [media](#media-props) | +`imageSize` | String | XS, Small, Medium, Large, XL or XXL +`imageLazyLoad` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. +`imageHighestQuality`| Boolean | Calls image service with "quality=highest" option, works only with XXL images + #### Headshot Props From 87e60f35d05913324ae944a29da3970ab8dce6ca Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Tue, 6 Oct 2020 15:42:53 +0100 Subject: [PATCH 569/760] Add n-content-body class to live blog post body This fixes styling issues in ft-app --- components/x-live-blog-post/src/LiveBlogPost.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 327630060..b8e6efdfe 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -27,7 +27,7 @@ const LiveBlogPost = (props) => {
    {isBreakingNews &&
    Breaking news
    } {title &&

    {title}

    } -
    +
    {showShareButtons && }
    From 78cf5aa8c2f0225721661bdf05533280b3e78739 Mon Sep 17 00:00:00 2001 From: John Kavanagh Date: Tue, 6 Oct 2020 19:42:29 +0100 Subject: [PATCH 570/760] update storybook with knob for imageHighestQuality --- components/x-teaser/storybook/article.js | 1 + components/x-teaser/storybook/content-package.js | 1 + components/x-teaser/storybook/knobs.js | 5 ++++- components/x-teaser/storybook/opinion.js | 1 + components/x-teaser/storybook/package-item.js | 1 + components/x-teaser/storybook/podcast.js | 1 + components/x-teaser/storybook/promoted.js | 3 ++- components/x-teaser/storybook/top-story.js | 1 + components/x-teaser/storybook/video.js | 1 + 9 files changed, 13 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/storybook/article.js b/components/x-teaser/storybook/article.js index 79c5862db..bcdcb56f2 100644 --- a/components/x-teaser/storybook/article.js +++ b/components/x-teaser/storybook/article.js @@ -35,6 +35,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', + 'imageHighestQuality', // Indicators 'indicators', // Context diff --git a/components/x-teaser/storybook/content-package.js b/components/x-teaser/storybook/content-package.js index b96d5c6bc..6afc81fd0 100644 --- a/components/x-teaser/storybook/content-package.js +++ b/components/x-teaser/storybook/content-package.js @@ -32,6 +32,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', + 'imageHighestQuality', // Variants 'layout', 'theme', diff --git a/components/x-teaser/storybook/knobs.js b/components/x-teaser/storybook/knobs.js index f9f1d9989..80b6a390f 100644 --- a/components/x-teaser/storybook/knobs.js +++ b/components/x-teaser/storybook/knobs.js @@ -122,7 +122,10 @@ module.exports = (data, { object, text, number, boolean, date, select }) => { }, imageSize() { return select('Image size', ['XS', 'Small', 'Medium', 'Large', 'XL', 'XXL'], data.imageSize, Groups.Image); - } + }, + imageHighestQuality(){ + return boolean('Image hi-quality', data.imageHighestQuality, Groups.Image); + } }; const Headshot = { diff --git a/components/x-teaser/storybook/opinion.js b/components/x-teaser/storybook/opinion.js index 235bcf739..1e5e04188 100644 --- a/components/x-teaser/storybook/opinion.js +++ b/components/x-teaser/storybook/opinion.js @@ -38,6 +38,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', + 'imageHighestQuality', // Variants 'layout', 'modifiers', diff --git a/components/x-teaser/storybook/package-item.js b/components/x-teaser/storybook/package-item.js index 17dde23e3..8983cd05d 100644 --- a/components/x-teaser/storybook/package-item.js +++ b/components/x-teaser/storybook/package-item.js @@ -35,6 +35,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', + 'imageHighestQuality', // Indicators 'indicators', // Variants diff --git a/components/x-teaser/storybook/podcast.js b/components/x-teaser/storybook/podcast.js index 65b494525..49fa44ac6 100644 --- a/components/x-teaser/storybook/podcast.js +++ b/components/x-teaser/storybook/podcast.js @@ -35,6 +35,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', + 'imageHighestQuality', // Indicators 'indicators', // Context diff --git a/components/x-teaser/storybook/promoted.js b/components/x-teaser/storybook/promoted.js index 1d7d22ed2..070afd966 100644 --- a/components/x-teaser/storybook/promoted.js +++ b/components/x-teaser/storybook/promoted.js @@ -26,7 +26,8 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - // Variants + 'imageHighestQuality', + // Variants 'layout', 'modifiers' ]; diff --git a/components/x-teaser/storybook/top-story.js b/components/x-teaser/storybook/top-story.js index ef9bc6cb2..474a7a739 100644 --- a/components/x-teaser/storybook/top-story.js +++ b/components/x-teaser/storybook/top-story.js @@ -32,6 +32,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', + 'imageHighestQuality', // Related 'showRelatedLinks', 'relatedLinks', diff --git a/components/x-teaser/storybook/video.js b/components/x-teaser/storybook/video.js index c655bb2f7..736110442 100644 --- a/components/x-teaser/storybook/video.js +++ b/components/x-teaser/storybook/video.js @@ -30,6 +30,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', + 'imageHighestQuality', // Video 'showVideo', 'video', From 994da4affc7718d22ce24542e48785215eda6fb8 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 7 Oct 2020 12:27:50 +0100 Subject: [PATCH 571/760] Update snapshots --- .../__tests__/__snapshots__/snapshots.test.js.snap | 9 --------- 1 file changed, 9 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 766cb736b..c3790c292 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -387,7 +387,6 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` > Paid post - by @@ -870,7 +869,6 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with promoted data 1`] > Paid post - by @@ -1392,7 +1390,6 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1` > Paid post - by @@ -1803,7 +1800,6 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with promoted data 1`] > Paid post - by @@ -2417,7 +2413,6 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = ` > Paid post - by @@ -2864,7 +2859,6 @@ exports[`x-teaser / snapshots renders a Small teaser with promoted data 1`] = ` > Paid post - by @@ -3422,7 +3416,6 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`] > Paid post - by @@ -3941,7 +3934,6 @@ exports[`x-teaser / snapshots renders a TopStory teaser with promoted data 1`] = > Paid post - by @@ -4572,7 +4564,6 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d > Paid post - by From 0836193977295ceb8d5433048e76f29901ad4182 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 7 Oct 2020 12:49:46 +0100 Subject: [PATCH 572/760] Revert "Update snapshots" This reverts commit 994da4affc7718d22ce24542e48785215eda6fb8. --- .../__tests__/__snapshots__/snapshots.test.js.snap | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index c3790c292..766cb736b 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -387,6 +387,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with promoted data 1`] = ` > Paid post + by @@ -869,6 +870,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with promoted data 1`] > Paid post + by @@ -1390,6 +1392,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with promoted data 1` > Paid post + by @@ -1800,6 +1803,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with promoted data 1`] > Paid post + by @@ -2413,6 +2417,7 @@ exports[`x-teaser / snapshots renders a Large teaser with promoted data 1`] = ` > Paid post + by @@ -2859,6 +2864,7 @@ exports[`x-teaser / snapshots renders a Small teaser with promoted data 1`] = ` > Paid post + by @@ -3416,6 +3422,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with promoted data 1`] > Paid post + by @@ -3934,6 +3941,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with promoted data 1`] = > Paid post + by @@ -4564,6 +4572,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with promoted d > Paid post + by From 7debe342de326e0f39e5452c94146fb1bac96143 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 7 Oct 2020 15:17:47 +0100 Subject: [PATCH 573/760] Remove snyk from publish step in CircleCI temporarily Snyk job fails with a generic error. There is no way of debugging this. Currently the live blogs work is blocked by this. --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e118f4e35..210f624f9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -120,7 +120,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From cbb67b749efa5dd1e83a56b82bfe9aac72d33587 Mon Sep 17 00:00:00 2001 From: John Kavanagh Date: Fri, 9 Oct 2020 09:59:25 +0100 Subject: [PATCH 574/760] add tab --- components/x-teaser/src/Image.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index bb0820d16..3c02bacc8 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -29,7 +29,7 @@ const LazyImage = ({ src, lazyLoad }) => { export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, imageHighestQuality, ...props }) => { const displayUrl = relativeUrl || url; const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')); - const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {}; + const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {}; const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize], options) : image.url; const ImageComponent = imageLazyLoad ? LazyImage : NormalImage; From 7f0307b5bfca8b95745c7d4376462b15a7a78795 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Fri, 9 Oct 2020 14:42:15 +0100 Subject: [PATCH 575/760] Add article--body to the wrapper div of live blog post This is needed for the ft-app to render content correctly. --- components/x-live-blog-post/src/LiveBlogPost.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index b8e6efdfe..23e943a09 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -27,7 +27,7 @@ const LiveBlogPost = (props) => {
    {isBreakingNews &&
    Breaking news
    } {title &&

    {title}

    } -
    +
    {showShareButtons && }
    From 65413033e2df3c4278a4895cb653915cbd19e3fd Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Fri, 9 Oct 2020 15:08:15 +0100 Subject: [PATCH 576/760] Order live blog posts by date New posts will appear at the top --- .../x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 13 +++++++++++++ .../src/__tests__/LiveBlogWrapper.test.jsx | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 908261ad3..3e7c3fdbd 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -36,6 +36,19 @@ const withLiveBlogWrapperActions = withActions({ }); const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id }) => { + posts.sort((a, b) => { + // Newer posts on top + if (a.publishedDate > b.publishedDate) { + return -1; + } + + if (b.publishedDate > a.publishedDate) { + return 1; + } + + return 0; + }); + const postElements = posts.map(post => Post 1 body

    ', - publishedDate: new Date().toISOString(), + publishedDate: '2020-10-09T10:00:00.000Z', isBreakingNews: true, - articleUrl: 'Https://www.ft.com', + articleUrl: 'https://www.ft.com', showShareButtons: true }; @@ -17,9 +17,9 @@ const post2 = { id: '2', title: 'Post 2 Title', bodyHTML: '

    Post 2 body

    ', - publishedDate: new Date().toISOString(), + publishedDate: '2020-10-09T11:00:00.000Z', isBreakingNews: false, - articleUrl: 'Https://www.ft.com', + articleUrl: 'https://www.ft.com', showShareButtons: true } @@ -33,6 +33,15 @@ describe('x-live-blog-wrapper', () => { expect(liveBlogWrapper.html()).toContain('Post 2 Title'); expect(liveBlogWrapper.html()).toContain('Post 2 body'); }); + + it('orders posts by date - new posts on top', () => { + const posts = [ post1, post2 ]; + const liveBlogWrapper = mount(); + + const articles = liveBlogWrapper.find('article'); + expect(articles.at(0).html()).toContain('Post 2 Title'); + expect(articles.at(1).html()).toContain('Post 1 Title'); + }); }); describe('liveBlogWrapperActions', () => { From 27723d5b830f0ab9365d2ac05f925e4f6220d0fc Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Fri, 9 Oct 2020 15:14:29 +0100 Subject: [PATCH 577/760] Update CODEOWNERS for x-live-blog-wrapper --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 98075ac14..5b3748a6b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,4 +8,5 @@ components/x-privacy-manager @Financial-Times/ads components/x-teaser @Financial-Times/content-discovery components/x-teaser-timeline @Financial-Times/content-discovery components/x-live-blog-post @Financial-Times/content-innovation +components/x-live-blog-wrapper @Financial-Times/content-innovation components/x-gift-article @Financial-Times/accounts From 01375ffb42a88cccb0dee80d473db4525fb865bf Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Fri, 9 Oct 2020 15:30:37 +0100 Subject: [PATCH 578/760] Live blog post ordering also works on publishedTimestamp property --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 3e7c3fdbd..43b45de0e 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -37,12 +37,15 @@ const withLiveBlogWrapperActions = withActions({ const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id }) => { posts.sort((a, b) => { + const timestampA = a.publishedDate || a.publishedTimestamp; + const timestampB = b.publishedDate || b.publishedTimestamp; + // Newer posts on top - if (a.publishedDate > b.publishedDate) { + if (timestampA > timestampB) { return -1; } - if (b.publishedDate > a.publishedDate) { + if (timestampB > timestampA) { return 1; } From 24984503bb33142d544eaeb0fba4751310c21d4b Mon Sep 17 00:00:00 2001 From: John Kavanagh Date: Fri, 9 Oct 2020 10:29:38 +0100 Subject: [PATCH 579/760] remove future proofing and restore compatibility with node v6 --- components/x-teaser/src/concerns/image-service.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index 25132f75b..ac331eec9 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -1,9 +1,6 @@ +const { URL, URLSearchParams } = require('url'); const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw'; -const OPTIONS = Object.freeze({ - source: 'next', - fit: 'scale-down', - dpr: 2, -}); +const OPTIONS = { source:'next', fit:'scale-down', dpr:2 }; /** * Image Service From 822608e84ced2f7d5fcdcd81f861825a0a8d17e2 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Mon, 12 Oct 2020 16:08:35 +0100 Subject: [PATCH 580/760] Forward the ref callback to the main element that wraps live blogs To initialise the origami components on the live blog posts, FT app needs a reference to the DOM element that wraps the posts. This will be used in ft app like this: ``` import { createRef } from 'preact'; // in constructor this.liveBlogWrapper = createRef(); // in render // in componentDidMount const dateElements = this.liveBlogWrapperRef.current.querySelectorAll('query-for-o-date-elements'); // init oDate for each date element ``` --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 43b45de0e..093ea214b 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -35,7 +35,7 @@ const withLiveBlogWrapperActions = withActions({ } }); -const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id }) => { +const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id, liveBlogWrapperElementRef }) => { posts.sort((a, b) => { const timestampA = a.publishedDate || a.publishedTimestamp; const timestampB = b.publishedDate || b.publishedTimestamp; @@ -60,7 +60,7 @@ const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id }) = ); return ( -
    +
    {postElements}
    ); From 2702bd20feeb6a85c547135a9c191d002afa72f3 Mon Sep 17 00:00:00 2001 From: John Kavanagh Date: Wed, 14 Oct 2020 09:19:19 +0100 Subject: [PATCH 581/760] swap spaces for tabs --- components/x-teaser/src/concerns/image-service.js | 2 +- components/x-teaser/storybook/article.js | 2 +- components/x-teaser/storybook/content-package.js | 2 +- components/x-teaser/storybook/knobs.js | 6 +++--- components/x-teaser/storybook/opinion.js | 2 +- components/x-teaser/storybook/package-item.js | 2 +- components/x-teaser/storybook/podcast.js | 2 +- components/x-teaser/storybook/promoted.js | 4 ++-- components/x-teaser/storybook/top-story.js | 2 +- components/x-teaser/storybook/video.js | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index ac331eec9..cf42141d3 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -9,7 +9,7 @@ const OPTIONS = { source:'next', fit:'scale-down', dpr:2 }; * @param {String} options */ export default function imageService(url, width, options) { - const imageSrc = new URL(`${BASE_URL}/${encodeURIComponent(url)}`); + const imageSrc = new URL(`${BASE_URL}/${encodeURIComponent(url)}`); imageSrc.search = new URLSearchParams({...OPTIONS, ...options }); imageSrc.searchParams.set('width', width); return imageSrc.href; diff --git a/components/x-teaser/storybook/article.js b/components/x-teaser/storybook/article.js index bcdcb56f2..ef6860042 100644 --- a/components/x-teaser/storybook/article.js +++ b/components/x-teaser/storybook/article.js @@ -35,7 +35,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - 'imageHighestQuality', + 'imageHighestQuality', // Indicators 'indicators', // Context diff --git a/components/x-teaser/storybook/content-package.js b/components/x-teaser/storybook/content-package.js index 6afc81fd0..133c096cb 100644 --- a/components/x-teaser/storybook/content-package.js +++ b/components/x-teaser/storybook/content-package.js @@ -32,7 +32,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - 'imageHighestQuality', + 'imageHighestQuality', // Variants 'layout', 'theme', diff --git a/components/x-teaser/storybook/knobs.js b/components/x-teaser/storybook/knobs.js index 80b6a390f..decaf2c70 100644 --- a/components/x-teaser/storybook/knobs.js +++ b/components/x-teaser/storybook/knobs.js @@ -123,9 +123,9 @@ module.exports = (data, { object, text, number, boolean, date, select }) => { imageSize() { return select('Image size', ['XS', 'Small', 'Medium', 'Large', 'XL', 'XXL'], data.imageSize, Groups.Image); }, - imageHighestQuality(){ - return boolean('Image hi-quality', data.imageHighestQuality, Groups.Image); - } + imageHighestQuality(){ + return boolean('Image hi-quality', data.imageHighestQuality, Groups.Image); + } }; const Headshot = { diff --git a/components/x-teaser/storybook/opinion.js b/components/x-teaser/storybook/opinion.js index 1e5e04188..8ae64e50e 100644 --- a/components/x-teaser/storybook/opinion.js +++ b/components/x-teaser/storybook/opinion.js @@ -38,7 +38,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - 'imageHighestQuality', + 'imageHighestQuality', // Variants 'layout', 'modifiers', diff --git a/components/x-teaser/storybook/package-item.js b/components/x-teaser/storybook/package-item.js index 8983cd05d..9b47b0dec 100644 --- a/components/x-teaser/storybook/package-item.js +++ b/components/x-teaser/storybook/package-item.js @@ -35,7 +35,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - 'imageHighestQuality', + 'imageHighestQuality', // Indicators 'indicators', // Variants diff --git a/components/x-teaser/storybook/podcast.js b/components/x-teaser/storybook/podcast.js index 49fa44ac6..a5a086a0f 100644 --- a/components/x-teaser/storybook/podcast.js +++ b/components/x-teaser/storybook/podcast.js @@ -35,7 +35,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - 'imageHighestQuality', + 'imageHighestQuality', // Indicators 'indicators', // Context diff --git a/components/x-teaser/storybook/promoted.js b/components/x-teaser/storybook/promoted.js index 070afd966..6acd91c72 100644 --- a/components/x-teaser/storybook/promoted.js +++ b/components/x-teaser/storybook/promoted.js @@ -26,8 +26,8 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - 'imageHighestQuality', - // Variants + 'imageHighestQuality', + // Variants 'layout', 'modifiers' ]; diff --git a/components/x-teaser/storybook/top-story.js b/components/x-teaser/storybook/top-story.js index 474a7a739..8cfea9563 100644 --- a/components/x-teaser/storybook/top-story.js +++ b/components/x-teaser/storybook/top-story.js @@ -32,7 +32,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - 'imageHighestQuality', + 'imageHighestQuality', // Related 'showRelatedLinks', 'relatedLinks', diff --git a/components/x-teaser/storybook/video.js b/components/x-teaser/storybook/video.js index 736110442..608f9d93a 100644 --- a/components/x-teaser/storybook/video.js +++ b/components/x-teaser/storybook/video.js @@ -30,7 +30,7 @@ exports.knobs = [ 'showImage', 'image', 'imageSize', - 'imageHighestQuality', + 'imageHighestQuality', // Video 'showVideo', 'video', From 25b5f755a68aae17e380f60bcb93d064f1e1a04f Mon Sep 17 00:00:00 2001 From: andygout Date: Tue, 20 Oct 2020 08:16:08 +0100 Subject: [PATCH 582/760] Revise npx snyk monitor CircleCI config command --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 210f624f9..eaaf35dc7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -134,7 +134,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Extract tag name and version number command: | From e56ac1b6fb86e8832d570503ee79fda2bb053c0b Mon Sep 17 00:00:00 2001 From: andygout Date: Tue, 27 Oct 2020 17:26:51 +0000 Subject: [PATCH 583/760] Make CircleCI container config references version-agnostic --- .circleci/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index eaaf35dc7..d58d4021b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ references: # # Workspace # - container_config_node10: &container_config_node10 + container_config_node: &container_config_node working_directory: ~/project/build docker: - image: circleci/node:10.13 @@ -86,7 +86,7 @@ references: jobs: build: - <<: *container_config_node10 + <<: *container_config_node steps: - checkout - run: @@ -106,7 +106,7 @@ jobs: - build test: - <<: *container_config_node10 + <<: *container_config_node steps: - *attach_workspace - run: @@ -114,7 +114,7 @@ jobs: command: make test publish: - <<: *container_config_node10 + <<: *container_config_node steps: - *attach_workspace - run: @@ -128,7 +128,7 @@ jobs: command: npx athloi publish -- --access=public prerelease: - <<: *container_config_node10 + <<: *container_config_node steps: - *attach_workspace - run: @@ -153,7 +153,7 @@ jobs: command: npx athloi -F ${TARGET_MODULE} publish -- --access=public --tag=pre-release deploy: - <<: *container_config_node10 + <<: *container_config_node steps: - *attach_workspace - add_ssh_keys: From ec618c48929a64fe8e167037dc870dfe287fdf36 Mon Sep 17 00:00:00 2001 From: tbergmen <5130615+tbergmen@users.noreply.github.com> Date: Wed, 7 Oct 2020 16:37:14 +0100 Subject: [PATCH 584/760] Revert "Remove snyk from publish step in CircleCI temporarily" This reverts commit 7debe342de326e0f39e5452c94146fb1bac96143. --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index d58d4021b..6054f330c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -120,6 +120,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From cb6c973d293ffa09033726868fdb57a01885cd4a Mon Sep 17 00:00:00 2001 From: andygout Date: Wed, 28 Oct 2020 08:29:10 +0000 Subject: [PATCH 585/760] Add --prune-repeated-subdependencies flag to npx snyk monitor command --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6054f330c..68a822a92 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -120,7 +120,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From 86979f6ba82df4ea8982b66e11cb8fa644ae442c Mon Sep 17 00:00:00 2001 From: Nick Colley Date: Wed, 28 Oct 2020 11:21:18 +0000 Subject: [PATCH 586/760] Sync prettier setup from Page Kit Update x-dash to have a pre-commit hook for staged files which mirrors Page Kit's setup. This will prevent some style discussions in the review stage. --- .eslintrc.js | 6 ++++-- .prettierrc | 9 +++++++++ .prettierrc.json | 8 -------- package.json | 16 ++++++++++++++++ 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 .prettierrc delete mode 100644 .prettierrc.json diff --git a/.eslintrc.js b/.eslintrc.js index 1f7c10720..5d5b914a8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + parser: '@typescript-eslint/parser', env: { node: true, browser: true, @@ -12,7 +13,8 @@ module.exports = { // https://github.com/yannickcr/eslint-plugin-react 'plugin:react/recommended', // https://github.com/evcohen/eslint-plugin-jsx-a11y - 'plugin:jsx-a11y/recommended' + 'plugin:jsx-a11y/recommended', + 'prettier' ], parserOptions: { ecmaFeatures: { @@ -53,4 +55,4 @@ module.exports = { } } ] -}; +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..5e454b3c5 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "printWidth": 110, + "semi": false, + "singleQuote": true, + "bracketSpacing": true, + "arrowParens": "always", + "jsxBracketSameLine": true, + "trailingComma": "none" +} diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index c2f098c4c..000000000 --- a/.prettierrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "printWidth": 100, - "semi": true, - "singleQuote": true, - "bracketSpacing": true, - "arrowParens": "always", - "jsxBracketSameLine": true -} diff --git a/package.json b/package.json index 60e22cc16..0e179c17c 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,12 @@ "heroku-postbuild": "make install && npm run build", "prepare": "npx snyk protect || npx snyk protect -d || true" }, + "lint-staged": { + "**/*.{ts,tsx,js,jsx}": [ + "prettier --write", + "eslint --fix" + ] + }, "devDependencies": { "@babel/core": "^7.4.5", "@financial-times/athloi": "^1.0.0-beta.26", @@ -23,6 +29,7 @@ "@storybook/addon-viewport": "^5.1.8", "@storybook/react": "^5.1.8", "@types/jest": "26.0.0", + "@typescript-eslint/parser": "^3.0.0", "babel-loader": "^8.0.4", "copy-webpack-plugin": "^5.0.2", "core-js": "^2.6.8", @@ -32,14 +39,18 @@ "eslint-plugin-jsx-a11y": "^6.2.0", "eslint-plugin-react": "^7.13.0", "fetch-mock": "^7.3.3", + "husky": "^4.0.0", "jest": "^24.8.0", + "lint-staged": "^10.0.0", "node-sass": "^4.12.0", + "prettier": "^2.0.2", "react": "^16.8.6", "react-helmet": "^5.2.0", "react-test-renderer": "^16.8.6", "sass-loader": "^7.1.0", "snyk": "^1.168.0", "style-loader": "^0.23.1", + "typescript": "^3.9.5", "write-file-webpack-plugin": "^4.5.0" }, "x-dash": { @@ -48,6 +59,11 @@ "server": "react" } }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, "workspaces": [ "components/*", "packages/*", From f40c7d1816fa9eb9b33633dc47113b2455b8c7a6 Mon Sep 17 00:00:00 2001 From: Nick Colley Date: Wed, 28 Oct 2020 13:05:58 +0000 Subject: [PATCH 587/760] Run linting on all files with Prettier and Eslint --- .storybook/build-service.js | 30 +- .storybook/build-story.js | 52 +-- .storybook/config.js | 20 +- .storybook/register-components.js | 6 +- .storybook/webpack.config.js | 57 ++- __mocks__/styleMock.js | 2 +- __tests__/snapshots.test.js | 40 +- .../__tests__/x-follow-button.test.jsx | 194 ++++----- components/x-follow-button/rollup.js | 6 +- .../x-follow-button/src/FollowButton.jsx | 42 +- .../x-follow-button/storybook/index.jsx | 27 +- components/x-gift-article/rollup.js | 6 +- components/x-gift-article/src/Buttons.jsx | 72 ++-- .../x-gift-article/src/CopyConfirmation.jsx | 28 +- components/x-gift-article/src/Form.jsx | 46 +-- components/x-gift-article/src/GiftArticle.jsx | 134 ++++--- components/x-gift-article/src/Loading.jsx | 10 +- components/x-gift-article/src/Message.jsx | 66 ++-- .../x-gift-article/src/MobileShareButtons.jsx | 50 ++- .../src/RadioButtonsSection.jsx | 30 +- components/x-gift-article/src/Title.jsx | 14 +- components/x-gift-article/src/Url.jsx | 30 +- components/x-gift-article/src/UrlSection.jsx | 94 ++--- components/x-gift-article/src/lib/api.js | 53 +-- .../x-gift-article/src/lib/constants.js | 4 +- .../src/lib/share-link-actions.js | 48 ++- components/x-gift-article/src/lib/tracking.js | 51 +-- components/x-gift-article/src/lib/updaters.js | 55 ++- .../x-gift-article/stories/error-response.js | 34 +- .../x-gift-article/stories/free-article.js | 42 +- components/x-gift-article/stories/index.js | 10 +- .../x-gift-article/stories/native-share.js | 62 ++- .../stories/with-gift-credits.js | 62 ++- .../x-gift-article/stories/with-gift-link.js | 30 +- .../stories/without-gift-credits.js | 42 +- .../__tests__/x-increment.test.jsx | 36 +- components/x-increment/rollup.js | 6 +- components/x-increment/src/Increment.jsx | 41 +- components/x-increment/storybook/index.jsx | 16 +- .../__tests__/x-interaction.test.jsx | 372 ++++++++---------- components/x-interaction/rollup.js | 6 +- components/x-interaction/src/Hydrate.jsx | 77 ++-- .../x-interaction/src/HydrationData.jsx | 20 +- components/x-interaction/src/Interaction.jsx | 88 ++--- .../x-interaction/src/InteractionClass.jsx | 59 ++- .../x-interaction/src/InteractionRender.jsx | 13 +- .../x-interaction/src/InteractionSSR.jsx | 24 +- .../src/concerns/get-component-name.js | 7 +- .../x-interaction/src/concerns/map-values.js | 16 +- .../src/concerns/register-component.js | 6 +- .../x-interaction/src/concerns/serialiser.js | 34 +- .../src/concerns/wrap-component-name.js | 10 +- components/x-live-blog-post/rollup.js | 6 +- .../x-live-blog-post/src/LiveBlogPost.jsx | 29 +- .../x-live-blog-post/src/ShareButtons.jsx | 36 +- components/x-live-blog-post/src/Timestamp.jsx | 45 ++- .../src/__tests__/LiveBlogPost.test.jsx | 126 +++--- .../src/__tests__/ShareButtons.test.jsx | 42 +- .../src/__tests__/Timestamp.test.jsx | 75 ++-- .../x-live-blog-post/storybook/index.jsx | 37 +- components/x-live-blog-wrapper/rollup.js | 6 +- .../src/LiveBlogWrapper.jsx | 68 ++-- .../src/LiveEventListener.js | 67 ++-- .../src/__tests__/LiveBlogWrapper.test.jsx | 78 ++-- .../x-live-blog-wrapper/storybook/index.jsx | 28 +- components/x-podcast-launchers/rollup.js | 6 +- .../src/PodcastLaunchers.jsx | 101 +++-- .../src/__tests__/PodcastLaunchers.test.jsx | 33 +- .../src/__tests__/generate-app-links.test.js | 9 +- .../src/__tests__/generate-rss-url.test.js | 27 +- .../src/config/app-links.js | 43 +- .../src/config/series-ids.js | 6 +- .../src/copy-to-clipboard.js | 30 +- .../src/generate-app-links.js | 12 +- .../src/generate-rss-url.js | 4 +- .../x-podcast-launchers/stories/example.js | 8 +- .../x-podcast-launchers/stories/index.js | 14 +- .../x-podcast-launchers/stories/knobs.js | 2 +- components/x-privacy-manager/rollup.js | 6 +- .../src/__tests__/privacy-manager.test.jsx | 202 +++++----- components/x-privacy-manager/src/messages.jsx | 37 +- .../x-privacy-manager/src/privacy-manager.jsx | 107 +++-- .../x-privacy-manager/src/radio-btn.jsx | 12 +- components/x-privacy-manager/src/types.d.ts | 74 ++-- .../x-privacy-manager/storybook/data.js | 21 +- .../x-privacy-manager/storybook/index.js | 16 +- .../x-privacy-manager/storybook/knobs.js | 20 +- .../storybook/story-consent-accepted.js | 12 +- .../storybook/story-consent-blocked.js | 12 +- .../storybook/story-consent-indeterminate.js | 12 +- .../storybook/story-save-failed.js | 12 +- components/x-styling-demo/rollup.js | 6 +- components/x-styling-demo/src/Button.jsx | 22 +- components/x-styling-demo/stories/index.js | 16 +- components/x-styling-demo/stories/styling.js | 13 +- .../__tests__/TeaserTimeline.test.jsx | 199 +++++----- .../__tests__/lib/transform.test.js | 281 ++++++------- components/x-teaser-timeline/rollup.js | 6 +- .../x-teaser-timeline/src/TeaserTimeline.jsx | 107 ++--- components/x-teaser-timeline/src/lib/date.js | 44 +-- .../x-teaser-timeline/src/lib/transform.js | 153 ++++--- components/x-teaser-timeline/stories/index.js | 14 +- components/x-teaser-timeline/stories/knobs.js | 27 +- .../x-teaser-timeline/stories/timeline.js | 8 +- components/x-teaser/Props.d.ts | 199 ++++++---- .../x-teaser/__tests__/snapshots.test.js | 22 +- components/x-teaser/rollup.js | 6 +- components/x-teaser/src/Container.jsx | 33 +- components/x-teaser/src/Content.jsx | 6 +- components/x-teaser/src/CustomSlot.jsx | 11 +- components/x-teaser/src/Headshot.jsx | 14 +- components/x-teaser/src/Image.jsx | 49 +-- components/x-teaser/src/Link.jsx | 18 +- components/x-teaser/src/LiveBlogStatus.jsx | 9 +- components/x-teaser/src/Meta.jsx | 12 +- components/x-teaser/src/MetaLink.jsx | 26 +- components/x-teaser/src/Promoted.jsx | 4 +- components/x-teaser/src/RelatedLinks.jsx | 9 +- components/x-teaser/src/RelativeTime.jsx | 24 +- components/x-teaser/src/Standfirst.jsx | 36 +- components/x-teaser/src/Status.jsx | 18 +- components/x-teaser/src/Teaser.jsx | 32 +- components/x-teaser/src/TimeStamp.jsx | 6 +- components/x-teaser/src/Title.jsx | 31 +- components/x-teaser/src/Video.jsx | 52 +-- components/x-teaser/src/concerns/constants.js | 8 +- components/x-teaser/src/concerns/date-time.js | 18 +- .../x-teaser/src/concerns/image-service.js | 14 +- components/x-teaser/src/concerns/presets.js | 22 +- components/x-teaser/src/concerns/rules.js | 20 +- components/x-teaser/storybook/article.js | 10 +- .../x-teaser/storybook/content-package.js | 10 +- components/x-teaser/storybook/index.js | 12 +- components/x-teaser/storybook/knobs.js | 150 ++++--- components/x-teaser/storybook/opinion.js | 10 +- components/x-teaser/storybook/package-item.js | 10 +- components/x-teaser/storybook/podcast.js | 10 +- components/x-teaser/storybook/promoted.js | 10 +- components/x-teaser/storybook/top-story.js | 10 +- components/x-teaser/storybook/video.js | 10 +- jest.config.js | 2 +- packages/x-babel-config/index.js | 2 +- packages/x-babel-config/jest.js | 8 +- packages/x-engine/src/client.js | 12 +- packages/x-engine/src/concerns/deep-get.js | 12 +- .../x-engine/src/concerns/format-config.js | 18 +- packages/x-engine/src/concerns/presets.js | 2 +- .../x-engine/src/concerns/resolve-peer.js | 4 +- packages/x-engine/src/concerns/resolve-pkg.js | 4 +- packages/x-engine/src/server.js | 36 +- packages/x-engine/src/webpack.js | 58 +-- .../x-handlebars/concerns/resolve-local.js | 4 +- .../x-handlebars/concerns/resolve-peer.js | 4 +- packages/x-handlebars/index.js | 48 ++- packages/x-logo/src/color.js | 20 +- packages/x-logo/src/index.js | 45 +-- packages/x-logo/src/noise.js | 10 +- packages/x-logo/src/polygon.js | 8 +- packages/x-logo/src/triangles.js | 8 +- packages/x-logo/src/util.js | 3 +- packages/x-node-jsx/index.js | 22 +- packages/x-node-jsx/register.js | 2 +- packages/x-rollup/index.js | 20 +- packages/x-rollup/src/babel-config.js | 12 +- packages/x-rollup/src/bundle.js | 12 +- packages/x-rollup/src/logger.js | 30 +- packages/x-rollup/src/postcss-config.js | 6 +- packages/x-rollup/src/rollup-config.js | 46 ++- packages/x-rollup/src/watch.js | 36 +- packages/x-test-utils/enzyme.js | 10 +- private/blueprints/component/rollup.js | 6 +- .../blueprints/component/stories/example.js | 6 +- private/scripts/blueprint.js | 76 ++-- web/gatsby-config.js | 8 +- web/gatsby-node.js | 17 +- .../extend-node-type.js | 6 +- .../gatsby-node.js | 4 +- .../on-create-node.js | 18 +- web/src/components/footer/index.jsx | 9 +- web/src/components/header/index.jsx | 18 +- web/src/components/icon/index.jsx | 10 +- web/src/components/layouts/basic.jsx | 18 +- web/src/components/layouts/splash.jsx | 10 +- web/src/components/module-list/index.jsx | 6 +- web/src/components/sidebar/module-menu.jsx | 10 +- web/src/components/sidebar/pages-menu.jsx | 12 +- web/src/components/story-viewer/index.jsx | 12 +- web/src/components/tertiary/links.jsx | 8 +- web/src/components/tertiary/subheadings.jsx | 30 +- web/src/html.jsx | 11 +- web/src/lib/create-documentation-pages.js | 22 +- web/src/lib/create-npm-package-pages.js | 26 +- web/src/lib/decorate-nodes.js | 22 +- web/src/pages/components.jsx | 18 +- web/src/pages/index.jsx | 30 +- web/src/pages/packages.jsx | 18 +- web/src/templates/documentation-page.jsx | 14 +- web/src/templates/npm-package.jsx | 30 +- 198 files changed, 3362 insertions(+), 3315 deletions(-) diff --git a/.storybook/build-service.js b/.storybook/build-service.js index bbeb441a9..4c8267456 100644 --- a/.storybook/build-service.js +++ b/.storybook/build-service.js @@ -1,23 +1,25 @@ -import React from 'react'; -import { Helmet } from 'react-helmet'; +import React from 'react' +import { Helmet } from 'react-helmet' function buildServiceUrl(deps, type) { - const modules = Object.keys(deps).map((i) => `${i}@${deps[i]}`).join(','); - return `https://www.ft.com/__origami/service/build/v2/bundles/${type}?modules=${modules}`; + const modules = Object.keys(deps) + .map((i) => `${i}@${deps[i]}`) + .join(',') + return `https://www.ft.com/__origami/service/build/v2/bundles/${type}?modules=${modules}` } class BuildService extends React.Component { constructor(props) { - super(props); - this.initialised = []; + super(props) + this.initialised = [] } componentDidUpdate() { if (window.hasOwnProperty('Origami')) { for (const component in Origami) { if (typeof Origami[component].init === 'function') { - const instance = Origami[component].init(); - this.initialised.concat(instance); + const instance = Origami[component].init() + this.initialised.concat(instance) } } } @@ -26,22 +28,22 @@ class BuildService extends React.Component { componentWillUnmount() { this.initialised.forEach((instance) => { if (typeof instance.destroy === 'function') { - instance.destroy(); + instance.destroy() } - }); + }) } render() { - const js = buildServiceUrl(this.props.dependencies, 'js'); - const css = buildServiceUrl(this.props.dependencies, 'css'); + const js = buildServiceUrl(this.props.dependencies, 'js') + const css = buildServiceUrl(this.props.dependencies, 'css') return ( - ); + ) } } -export default BuildService; +export default BuildService diff --git a/.storybook/build-story.js b/.storybook/build-story.js index a7ccfe2da..6b08a6357 100644 --- a/.storybook/build-story.js +++ b/.storybook/build-story.js @@ -1,15 +1,15 @@ -import React from 'react'; -import BuildService from './build-service'; -import { storiesOf } from '@storybook/react'; -import * as knobsAddon from '@storybook/addon-knobs'; -import { Helmet } from 'react-helmet'; -import path from 'path'; +import React from 'react' +import BuildService from './build-service' +import { storiesOf } from '@storybook/react' +import * as knobsAddon from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' +import path from 'path' // HACK: The browser bundle for Fetch Mock implicitly depends on core-js 2.x so ensure // that this is an explicit dependency of the repository root. -import fetchMock from 'fetch-mock'; +import fetchMock from 'fetch-mock' -const defaultKnobs = () => ({}); +const defaultKnobs = () => ({}) /** * Create Props @@ -19,28 +19,28 @@ const defaultKnobs = () => ({}); */ function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs) { // Inject knobs add-on into given dependency container - const knobs = hydrateKnobs(defaultData, knobsAddon); + const knobs = hydrateKnobs(defaultData, knobsAddon) // Mix the available knob props into default data - const mixedProps = { ...defaultData, ...knobs }; + const mixedProps = { ...defaultData, ...knobs } if (allowedKnobs.length === 0) { - return mixedProps; + return mixedProps } return allowedKnobs.reduce((map, prop) => { if (mixedProps.hasOwnProperty(prop)) { - const value = mixedProps[prop]; + const value = mixedProps[prop] // Knobs are functions which need calling to register them if (typeof value === 'function') { - map[prop] = value(); + map[prop] = value() } else { - map[prop] = value; + map[prop] = value } } - return map; - }, {}); + return map + }, {}) } /** @@ -52,17 +52,17 @@ function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs * @param {{ title: String, data: {}, knobs: String[], m: module }} story */ function buildStory({ package: pkg, dependencies, component: Component, knobs, story }) { - const name = path.basename(pkg.name); - const storybook = storiesOf(name, story.m); + const name = path.basename(pkg.name) + const storybook = storiesOf(name, story.m) - storybook.addDecorator(knobsAddon.withKnobs); + storybook.addDecorator(knobsAddon.withKnobs) storybook.add(story.title, () => { - const props = createProps(story.data, story.knobs, knobs); + const props = createProps(story.data, story.knobs, knobs) if (story.fetchMock) { - fetchMock.restore(); // to isolate the mocks to each story - story.fetchMock(fetchMock); + fetchMock.restore() // to isolate the mocks to each story + story.fetchMock(fetchMock) } return ( @@ -75,10 +75,10 @@ function buildStory({ package: pkg, dependencies, component: Component, knobs, s )}
    - ); - }); + ) + }) - return storybook; + return storybook } -export default buildStory; +export default buildStory diff --git a/.storybook/config.js b/.storybook/config.js index c6087005c..670f654f9 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -1,6 +1,6 @@ -import { configure } from '@storybook/react'; -import buildStory from './build-story'; -import * as components from './register-components'; +import { configure } from '@storybook/react' +import buildStory from './build-story' +import * as components from './register-components' configure(() => { // We used to use a configuration based technique for defining stories for each component as we @@ -8,12 +8,12 @@ configure(() => { // way as defined by the Storybook documentation: // components.forEach(({ stories, ...data }) => { - stories.forEach((story) => buildStory({ story, ...data })); - }); + stories.forEach((story) => buildStory({ story, ...data })) + }) // Add regular story definitions (i.e. those using storiesOf() directly below) - require('../components/x-increment/storybook/index.jsx'); - require('../components/x-follow-button/storybook/index.jsx'); - require('../components/x-live-blog-post/storybook/index.jsx'); - require('../components/x-live-blog-wrapper/storybook/index.jsx'); -}, module); + require('../components/x-increment/storybook/index.jsx') + require('../components/x-follow-button/storybook/index.jsx') + require('../components/x-live-blog-post/storybook/index.jsx') + require('../components/x-live-blog-wrapper/storybook/index.jsx') +}, module) diff --git a/.storybook/register-components.js b/.storybook/register-components.js index 658106f75..0225842e8 100644 --- a/.storybook/register-components.js +++ b/.storybook/register-components.js @@ -6,7 +6,7 @@ const components = [ require('../components/x-gift-article/stories'), require('../components/x-podcast-launchers/stories'), require('../components/x-teaser-timeline/stories'), - require('../components/x-privacy-manager/storybook'), -]; + require('../components/x-privacy-manager/storybook') +] -module.exports = components; +module.exports = components diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 70520a4c8..ab6a7aa94 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -1,47 +1,46 @@ // This configuration extends the existing Storybook Webpack config. // See https://storybook.js.org/configurations/custom-webpack-config/ for more info. -const path = require('path'); -const glob = require('glob'); -const fs = require('fs'); -const xBabelConfig = require('../packages/x-babel-config'); -const xEngine = require('../packages/x-engine/src/webpack'); -const CopyPlugin = require('copy-webpack-plugin'); -const WritePlugin = require('write-file-webpack-plugin'); +const path = require('path') +const glob = require('glob') +const fs = require('fs') +const xBabelConfig = require('../packages/x-babel-config') +const xEngine = require('../packages/x-engine/src/webpack') +const CopyPlugin = require('copy-webpack-plugin') +const WritePlugin = require('write-file-webpack-plugin') -const excludePaths = [/node_modules/, /dist/]; +const excludePaths = [/node_modules/, /dist/] const cssCopy = fs.readdirSync(path.resolve('components')).reduce((mains, component) => { - const componentPkg = path.resolve('components', component, 'package.json'); + const componentPkg = path.resolve('components', component, 'package.json') if (fs.existsSync(componentPkg)) { - const pkg = require(componentPkg); + const pkg = require(componentPkg) if (pkg.style) { - const styleResolved = path.resolve('components', component, pkg.style); + const styleResolved = path.resolve('components', component, pkg.style) return mains.concat({ from: styleResolved, to: path.resolve(__dirname, 'static/components', path.basename(pkg.name), pkg.style) - }); + }) } } - return mains; -}, []); + return mains +}, []) module.exports = ({ config }) => { // HACK: extend existing JS rule to ensure all dependencies are correctly ignored // from Babel transpilation. // https://github.com/storybooks/storybook/issues/3346#issuecomment-459439438 - const jsRule = config.module.rules.find((rule) => rule.test.test('.jsx')); - jsRule.exclude = excludePaths; - + const jsRule = config.module.rules.find((rule) => rule.test.test('.jsx')) + jsRule.exclude = excludePaths // HACK: Instruct Babel to check module type before injecting Core JS polyfills // https://github.com/i-like-robots/broken-webpack-bundle-test-case - const babelConfig = jsRule.use.find(({ loader }) => loader === 'babel-loader'); - babelConfig.options.sourceType = 'unambiguous'; + const babelConfig = jsRule.use.find(({ loader }) => loader === 'babel-loader') + babelConfig.options.sourceType = 'unambiguous' // Override the Babel configuration for all x- components with our own babelConfig.options.overrides = [ @@ -49,15 +48,15 @@ module.exports = ({ config }) => { test: /\/components\/x-[^\/]+\/src\//, ...xBabelConfig() } - ]; + ] // HACK: there is a bug in babel-plugin-minify-simplify which cannot // handle how Babel transpiles restful destructing assignment so remove it. // e.g. const { foo, ...qux } = { foo: 0, bar: 1, baz: 2 } babelConfig.options.presets = babelConfig.options.presets.filter((preset) => { - const name = Array.isArray(preset) ? preset[0] : preset; - return name.includes('babel-preset-minify') === false; - }); + const name = Array.isArray(preset) ? preset[0] : preset + return name.includes('babel-preset-minify') === false + }) config.module.rules.push({ test: /\.(scss|sass)$/, @@ -76,16 +75,16 @@ module.exports = ({ config }) => { { loader: require.resolve('sass-loader'), options: { - includePaths: glob.sync('./components/*/bower_components', { absolute: true }), + includePaths: glob.sync('./components/*/bower_components', { absolute: true }) } } ] - }); + }) // HACK: Ensure we only bundle one instance of React - config.resolve.alias.react = require.resolve('react'); + config.resolve.alias.react = require.resolve('react') - config.plugins.push(xEngine(), new CopyPlugin(cssCopy), new WritePlugin()); + config.plugins.push(xEngine(), new CopyPlugin(cssCopy), new WritePlugin()) - return config; -}; + return config +} diff --git a/__mocks__/styleMock.js b/__mocks__/styleMock.js index f053ebf79..4ba52ba2c 100644 --- a/__mocks__/styleMock.js +++ b/__mocks__/styleMock.js @@ -1 +1 @@ -module.exports = {}; +module.exports = {} diff --git a/__tests__/snapshots.test.js b/__tests__/snapshots.test.js index 692fe6227..ed3c6b42a 100644 --- a/__tests__/snapshots.test.js +++ b/__tests__/snapshots.test.js @@ -1,33 +1,31 @@ -const renderer = require('react-test-renderer'); -const fs = require('fs'); -const path = require('path'); -const glob = require('glob'); -const { h } = require('../packages/x-engine'); +const renderer = require('react-test-renderer') +const fs = require('fs') +const path = require('path') +const glob = require('glob') +const { h } = require('../packages/x-engine') -const { workspaces } = require('../package.json'); +const { workspaces } = require('../package.json') -const packagesGlob = workspaces.length > 1 - ? `{${workspaces.join(',')}}` - : workspaces[0]; +const packagesGlob = workspaces.length > 1 ? `{${workspaces.join(',')}}` : workspaces[0] -const packageDirs = glob.sync(packagesGlob); +const packageDirs = glob.sync(packagesGlob) -for(const pkg of packageDirs) { - const pkgDir = path.resolve(pkg); - const storiesDir = path.resolve(pkgDir, 'stories'); +for (const pkg of packageDirs) { + const pkgDir = path.resolve(pkg) + const storiesDir = path.resolve(pkgDir, 'stories') - if(fs.existsSync(storiesDir)) { - const { package: pkg, stories, component } = require(storiesDir); - const { presets = { default: {} } } = require(pkgDir); - const name = path.basename(pkg.name); + if (fs.existsSync(storiesDir)) { + const { package: pkg, stories, component } = require(storiesDir) + const { presets = { default: {} } } = require(pkgDir) + const name = path.basename(pkg.name) for (const { title, data } of stories) { for (const [preset, options] of Object.entries(presets)) { it(`${pkg.name} renders a ${preset} ${title} ${name}`, () => { - const props = { ...data, ...options }; - const tree = renderer.create(h(component, props)).toJSON(); - expect(tree).toMatchSnapshot(); - }); + const props = { ...data, ...options } + const tree = renderer.create(h(component, props)).toJSON() + expect(tree).toMatchSnapshot() + }) } } } diff --git a/components/x-follow-button/__tests__/x-follow-button.test.jsx b/components/x-follow-button/__tests__/x-follow-button.test.jsx index ae149fc84..b4b0337e6 100644 --- a/components/x-follow-button/__tests__/x-follow-button.test.jsx +++ b/components/x-follow-button/__tests__/x-follow-button.test.jsx @@ -1,174 +1,174 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -import { FollowButton } from '../src/FollowButton'; +import { FollowButton } from '../src/FollowButton' describe('x-follow-button', () => { describe('concept name', () => { it('when conceptNameAsButtonText prop is true, and the topic name is provided, the button is named by this name', () => { const subject = mount( - ); - expect(subject.find('button').text()).toEqual('dummy concept name'); - }); + ) + expect(subject.find('button').text()).toEqual('dummy concept name') + }) it('when conceptNameAsButtonText prop is false, the button has a default name', () => { const subject = mount( - ); - expect(subject.find('button').text()).toEqual('Add to myFT'); - }); + ) + expect(subject.find('button').text()).toEqual('Add to myFT') + }) it('when conceptNameAsButtonText prop is true, and the topic name is not provided, the button has a default name', () => { - const subject = mount(); - expect(subject.find('button').text()).toEqual('Add to myFT'); - }); + const subject = mount() + expect(subject.find('button').text()).toEqual('Add to myFT') + }) it('when conceptNameAsButtonText prop is not provided, the button has a default name', () => { - const subject = mount(); - expect(subject.find('button').text()).toEqual('Add to myFT'); - }); - }); + const subject = mount() + expect(subject.find('button').text()).toEqual('Add to myFT') + }) + }) describe('conceptId prop', () => { it('assigns conceptId prop to data-concept-id attribute of the button', async () => { - const subject = mount(); - expect(subject.find('button').prop('data-concept-id')).toEqual('dummy-id'); - }); + const subject = mount() + expect(subject.find('button').prop('data-concept-id')).toEqual('dummy-id') + }) it('assigns conceptId prop to data-concept-id attribute of the form', async () => { - const subject = mount(); - expect(subject.find('form').prop('data-concept-id')).toEqual('dummy-id'); - }); - }); + const subject = mount() + expect(subject.find('form').prop('data-concept-id')).toEqual('dummy-id') + }) + }) describe('form action', () => { it('assigns follow-plus-digest-email put action if followPlusDigestEmail is true', async () => { - const subject = mount(); + const subject = mount() expect(subject.find('form').prop('action')).toEqual( '/__myft/api/core/follow-plus-digest-email/dummy-id?method=put' - ); - }); + ) + }) it('assigns followed/concept delete action if isFollowed is true', async () => { - const subject = mount(); + const subject = mount() expect(subject.find('form').prop('action')).toEqual( '/__myft/api/core/followed/concept/dummy-id?method=delete' - ); - }); + ) + }) it('assigns followed/concept put action if isFollowed and followPlusDigestEmail are not passed', async () => { - const subject = mount(); + const subject = mount() expect(subject.find('form').prop('action')).toEqual( '/__myft/api/core/followed/concept/dummy-id?method=put' - ); - }); - }); + ) + }) + }) describe('isFollowed', () => { describe('when true', () => { it('button text is "Added"', () => { - const subject = mount(); - expect(subject.find('button').text()).toEqual('Added'); - }); + const subject = mount() + expect(subject.find('button').text()).toEqual('Added') + }) it('button aria-pressed is "true"', () => { - const subject = mount(); - expect(subject.find('button').prop('aria-pressed')).toEqual('true'); - }); + const subject = mount() + expect(subject.find('button').prop('aria-pressed')).toEqual('true') + }) it('button title is "Remove ConceptName from myFT"', () => { - const subject = mount(); - expect(subject.find('button').prop('title')).toEqual('Remove ConceptName from myFT'); - }); + const subject = mount() + expect(subject.find('button').prop('title')).toEqual('Remove ConceptName from myFT') + }) it('button aria-label is "Remove conceptName from myFT"', () => { - const subject = mount(); - expect(subject.find('button').prop('aria-label')).toEqual('Remove ConceptName from myFT'); - }); - }); + const subject = mount() + expect(subject.find('button').prop('aria-label')).toEqual('Remove ConceptName from myFT') + }) + }) describe('when false', () => { it('button text is "Add to myFT"', () => { - const subject = mount(); - expect(subject.find('button').text()).toEqual('Add to myFT'); - }); + const subject = mount() + expect(subject.find('button').text()).toEqual('Add to myFT') + }) it('button aria-pressed is "false"', () => { - const subject = mount(); - expect(subject.find('button').prop('aria-pressed')).toEqual('false'); - }); + const subject = mount() + expect(subject.find('button').prop('aria-pressed')).toEqual('false') + }) it('button title is "Add ConceptName to myFT"', () => { - const subject = mount(); - expect(subject.find('button').prop('title')).toEqual('Add ConceptName to myFT'); - }); + const subject = mount() + expect(subject.find('button').prop('title')).toEqual('Add ConceptName to myFT') + }) it('button aria-label is "Add ConceptName to myFT"', () => { - const subject = mount(); - expect(subject.find('button').prop('aria-label')).toEqual('Add ConceptName to myFT'); - }); - }); - }); + const subject = mount() + expect(subject.find('button').prop('aria-label')).toEqual('Add ConceptName to myFT') + }) + }) + }) describe('followPlusDigestEmail', () => { describe('when true', () => { it('form has data-myft-ui-variant property which is true', () => { - const subject = mount(); - expect(subject.find('form').prop('data-myft-ui-variant')).toEqual(true); - }); + const subject = mount() + expect(subject.find('form').prop('data-myft-ui-variant')).toEqual(true) + }) it('button has data-trackable-context-messaging property which is add-to-myft-plus-digest-button', () => { - const subject = mount(); + const subject = mount() expect(subject.find('button').prop('data-trackable-context-messaging')).toEqual( 'add-to-myft-plus-digest-button' - ); - }); - }); + ) + }) + }) describe('when false', () => { it('form has data-myft-ui-variant property which is true', () => { - const subject = mount(); - expect(subject.find('form').prop('data-myft-ui-variant')).toEqual(undefined); - }); + const subject = mount() + expect(subject.find('form').prop('data-myft-ui-variant')).toEqual(undefined) + }) it('button has data-trackable-context-messaging property which is add-to-myft-plus-digest-button', () => { - const subject = mount(); - expect(subject.find('button').prop('data-trackable-context-messaging')).toEqual(null); - }); - }); - }); + const subject = mount() + expect(subject.find('button').prop('data-trackable-context-messaging')).toEqual(null) + }) + }) + }) describe('form properties', () => { it('method = GET', () => { - const subject = mount(); - expect(subject.find('form').prop('method')).toEqual('GET'); - }); - }); + const subject = mount() + expect(subject.find('form').prop('method')).toEqual('GET') + }) + }) describe('button properties', () => { it('data-trackable="follow"', () => { - const subject = mount(); - expect(subject.find('button').prop('data-trackable')).toEqual('follow'); - }); + const subject = mount() + expect(subject.find('button').prop('data-trackable')).toEqual('follow') + }) it('type="submit"', () => { - const subject = mount(); - expect(subject.find('button').prop('type')).toEqual('submit'); - }); - }); + const subject = mount() + expect(subject.find('button').prop('type')).toEqual('submit') + }) + }) describe('csrf token', () => { it('if passed creates an invisible input field', () => { - const subject = mount(); - expect(subject.find('input').prop('value')).toEqual('dummyToken'); - expect(subject.find('input').prop('type')).toEqual('hidden'); - expect(subject.find('input').prop('name')).toEqual('token'); - expect(subject.find('input').prop('data-myft-csrf-token')).toEqual(true); - }); + const subject = mount() + expect(subject.find('input').prop('value')).toEqual('dummyToken') + expect(subject.find('input').prop('type')).toEqual('hidden') + expect(subject.find('input').prop('name')).toEqual('token') + expect(subject.find('input').prop('data-myft-csrf-token')).toEqual(true) + }) it('if not passed an invisible input field is not created', () => { - const subject = mount(); - expect(subject.find('input')).toEqual({}); - }); - }); -}); + const subject = mount() + expect(subject.find('input')).toEqual({}) + }) + }) +}) diff --git a/components/x-follow-button/rollup.js b/components/x-follow-button/rollup.js index 075823328..9347ae8b4 100644 --- a/components/x-follow-button/rollup.js +++ b/components/x-follow-button/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/FollowButton.jsx', pkg }); +xRollup({ input: './src/FollowButton.jsx', pkg }) diff --git a/components/x-follow-button/src/FollowButton.jsx b/components/x-follow-button/src/FollowButton.jsx index 807e28a72..9c5d5ffb2 100644 --- a/components/x-follow-button/src/FollowButton.jsx +++ b/components/x-follow-button/src/FollowButton.jsx @@ -1,6 +1,6 @@ -import { h } from '@financial-times/x-engine'; -import classNames from 'classnames'; -import styles from './styles/main.scss'; +import { h } from '@financial-times/x-engine' +import classNames from 'classnames' +import styles from './styles/main.scss' export const FollowButton = (props) => { const { @@ -12,29 +12,29 @@ export const FollowButton = (props) => { followPlusDigestEmail, onSubmit, variant - } = props; - const VARIANTS = ['standard', 'inverse', 'opinion', 'monochrome']; + } = props + const VARIANTS = ['standard', 'inverse', 'opinion', 'monochrome'] const getFormAction = () => { if (followPlusDigestEmail) { - return `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put`; + return `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put` } else if (isFollowed) { - return `/__myft/api/core/followed/concept/${conceptId}?method=delete`; + return `/__myft/api/core/followed/concept/${conceptId}?method=delete` } else { - return `/__myft/api/core/followed/concept/${conceptId}?method=put`; + return `/__myft/api/core/followed/concept/${conceptId}?method=put` } - }; + } const getButtonText = () => { if (conceptNameAsButtonText && conceptName) { - return conceptName; + return conceptName } - return isFollowed ? 'Added' : 'Add to myFT'; - }; + return isFollowed ? 'Added' : 'Add to myFT' + } const getAccessibleText = () => - isFollowed ? `Remove ${conceptName} from myFT` : `Add ${conceptName} to myFT`; + isFollowed ? `Remove ${conceptName} from myFT` : `Add ${conceptName} to myFT` return (
    { data-concept-id={conceptId} action={getFormAction()} onSubmit={(event) => { - event.preventDefault(); + event.preventDefault() const detail = { action: isFollowed ? 'remove' : 'add', actorType: 'user', @@ -51,13 +51,13 @@ export const FollowButton = (props) => { subjectType: 'concept', subjectId: conceptId, token: csrfToken - }; + } if (typeof onSubmit === 'function') { - onSubmit(detail); + onSubmit(detail) } - event.target.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })); + event.target.dispatchEvent(new CustomEvent('x-follow-button', { bubbles: true, detail })) }} {...(followPlusDigestEmail ? { 'data-myft-ui-variant': true } : null)}> {csrfToken && } @@ -69,13 +69,11 @@ export const FollowButton = (props) => { [styles[`button--${variant}`]]: VARIANTS.includes(variant) })} data-concept-id={conceptId} - data-trackable-context-messaging={ - followPlusDigestEmail ? 'add-to-myft-plus-digest-button' : null - } + data-trackable-context-messaging={followPlusDigestEmail ? 'add-to-myft-plus-digest-button' : null} data-trackable="follow" type="submit" dangerouslySetInnerHTML={{ __html: getButtonText() }} />
    - ); -}; + ) +} diff --git a/components/x-follow-button/storybook/index.jsx b/components/x-follow-button/storybook/index.jsx index 419b73185..98813d7c6 100644 --- a/components/x-follow-button/storybook/index.jsx +++ b/components/x-follow-button/storybook/index.jsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React from 'react' -import { storiesOf } from '@storybook/react'; -import { withKnobs, text, boolean, select } from '@storybook/addon-knobs'; +import { storiesOf } from '@storybook/react' +import { withKnobs, text, boolean, select } from '@storybook/addon-knobs' -import { FollowButton } from '../src/FollowButton'; +import { FollowButton } from '../src/FollowButton' const defaultProps = { isFollowed: false, @@ -13,16 +13,15 @@ const defaultProps = { conceptName: 'UK politics & policy', followPlusDigestEmail: true, csrfToken: 'testTokenValue' -}; +} const toggleConceptNameAsButtonText = () => - boolean('conceptNameAsButtonText', defaultProps.conceptNameAsButtonText); -const toggleIsFollowed = () => boolean('isFollowed', defaultProps.isFollowed); -const toggleConceptName = () => text('Topic name', defaultProps.conceptName); -const toggleFollowPlusDigestEmail = () => - boolean('followPlusDigestEmail', defaultProps.followPlusDigestEmail); + boolean('conceptNameAsButtonText', defaultProps.conceptNameAsButtonText) +const toggleIsFollowed = () => boolean('isFollowed', defaultProps.isFollowed) +const toggleConceptName = () => text('Topic name', defaultProps.conceptName) +const toggleFollowPlusDigestEmail = () => boolean('followPlusDigestEmail', defaultProps.followPlusDigestEmail) const toggleVariant = () => - select('variant', ['standard', 'inverse', 'opinion', 'monochrome'], defaultProps.variant); + select('variant', ['standard', 'inverse', 'opinion', 'monochrome'], defaultProps.variant) storiesOf('x-follow-button', module) .addDecorator(withKnobs) @@ -33,7 +32,7 @@ storiesOf('x-follow-button', module) conceptName: toggleConceptName(), followPlusDigestEmail: toggleFollowPlusDigestEmail(), variant: toggleVariant() - }; + } - return ; - }); + return + }) diff --git a/components/x-gift-article/rollup.js b/components/x-gift-article/rollup.js index f6112c551..8973a9a58 100644 --- a/components/x-gift-article/rollup.js +++ b/components/x-gift-article/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/GiftArticle.jsx', pkg }); +xRollup({ input: './src/GiftArticle.jsx', pkg }) diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index b716a1e5b..77d028e39 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -1,16 +1,12 @@ -import { h } from '@financial-times/x-engine'; -import { ShareType } from './lib/constants'; -import styles from './GiftArticle.scss'; +import { h } from '@financial-times/x-engine' +import { ShareType } from './lib/constants' +import styles from './GiftArticle.scss' -const ButtonsClassName = styles.buttons; +const ButtonsClassName = styles.buttons -const ButtonClassNames = styles['buttonBaseStyle']; +const ButtonClassNames = styles['buttonBaseStyle'] -const ButtonWithGapClassNames = [ - ButtonClassNames, - 'js-copy-link', - styles['button--with-gap'] -].join(' '); +const ButtonWithGapClassNames = [ButtonClassNames, 'js-copy-link', styles['button--with-gap']].join(' ') export default ({ shareType, @@ -21,55 +17,49 @@ export default ({ actions, giftCredits }) => { - if (isGiftUrlCreated || shareType === ShareType.nonGift) { - if (nativeShare) { return ( -
    -
    - ); + ) } return ( -
    - { showCopyButton && +
    + {showCopyButton && ( - } - - Email link to Share this article + )} + + Email link to Share this article
    - ); + ) } return ( -
    +
    - ); - -}; + ) +} diff --git a/components/x-gift-article/src/CopyConfirmation.jsx b/components/x-gift-article/src/CopyConfirmation.jsx index a7da749d4..ef6ad59db 100644 --- a/components/x-gift-article/src/CopyConfirmation.jsx +++ b/components/x-gift-article/src/CopyConfirmation.jsx @@ -1,5 +1,5 @@ -import { h } from '@financial-times/x-engine'; -import styles from './GiftArticle.scss'; +import { h } from '@financial-times/x-engine' +import styles from './GiftArticle.scss' const confirmationClassNames = [ styles['o-message'], @@ -7,20 +7,24 @@ const confirmationClassNames = [ styles['o-message--success'], styles['copy-confirmation'] -].join(' '); +].join(' ') export default ({ hideCopyConfirmation }) => ( -
    -
    - -
    -

    - The link has been copied to your clipboard +

    +
    +
    +

    + + The link has been copied to your clipboard +

    - - +
    -); +) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 7950fad05..bc6fc2fa2 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -1,33 +1,33 @@ -import { h } from '@financial-times/x-engine'; -import Title from './Title'; -import RadioButtonsSection from './RadioButtonsSection'; -import UrlSection from './UrlSection'; -import MobileShareButtons from './MobileShareButtons'; -import CopyConfirmation from './CopyConfirmation'; -import styles from './GiftArticle.scss'; +import { h } from '@financial-times/x-engine' +import Title from './Title' +import RadioButtonsSection from './RadioButtonsSection' +import UrlSection from './UrlSection' +import MobileShareButtons from './MobileShareButtons' +import CopyConfirmation from './CopyConfirmation' +import styles from './GiftArticle.scss' export default (props) => ( -
    -
    -
    +
    + +
    + - <Title title={ props.title }/> - - { !props.isFreeArticle && <RadioButtonsSection - shareType={ props.shareType } - showGiftUrlSection={ props.actions.showGiftUrlSection } - showNonGiftUrlSection={ props.actions.showNonGiftUrlSection }/> - } + {!props.isFreeArticle && ( + <RadioButtonsSection + shareType={props.shareType} + showGiftUrlSection={props.actions.showGiftUrlSection} + showNonGiftUrlSection={props.actions.showNonGiftUrlSection} + /> + )} <UrlSection {...props} /> </div> </form> - { props.showCopyConfirmation && - <CopyConfirmation hideCopyConfirmation={ props.actions.hideCopyConfirmation }/> } + {props.showCopyConfirmation && ( + <CopyConfirmation hideCopyConfirmation={props.actions.hideCopyConfirmation} /> + )} - { props.showMobileShareLinks && - <MobileShareButtons mobileShareLinks={ props.mobileShareLinks }/> } + {props.showMobileShareLinks && <MobileShareButtons mobileShareLinks={props.mobileShareLinks} />} </div> -); +) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 86142a2de..04daec969 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -1,127 +1,123 @@ -import { h } from '@financial-times/x-engine'; -import { withActions } from '@financial-times/x-interaction'; +import { h } from '@financial-times/x-engine' +import { withActions } from '@financial-times/x-interaction' -import Loading from './Loading'; -import Form from './Form'; +import Loading from './Loading' +import Form from './Form' -import ApiClient from './lib/api'; -import { copyToClipboard, createMailtoUrl } from './lib/share-link-actions'; -import tracking from './lib/tracking'; -import * as updaters from './lib/updaters'; +import ApiClient from './lib/api' +import { copyToClipboard, createMailtoUrl } from './lib/share-link-actions' +import tracking from './lib/tracking' +import * as updaters from './lib/updaters' -const isCopySupported = typeof document !== 'undefined' - && document.queryCommandSupported - && document.queryCommandSupported('copy'); +const isCopySupported = + typeof document !== 'undefined' && document.queryCommandSupported && document.queryCommandSupported('copy') const withGiftFormActions = withActions( - initialProps => { + (initialProps) => { const api = new ApiClient({ protocol: initialProps.apiProtocol, domain: initialProps.apiDomain - }); + }) return { showGiftUrlSection() { - return updaters.showGiftUrlSection; + return updaters.showGiftUrlSection }, showNonGiftUrlSection() { return async (state) => { - const update = updaters.showNonGiftUrlSection(state); + const update = updaters.showNonGiftUrlSection(state) if (!state.isNonGiftUrlShortened) { - const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift); + const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift) if (isShortened) { - Object.assign( - update, - updaters.setShortenedNonGiftUrl(url)(state) - ); + Object.assign(update, updaters.setShortenedNonGiftUrl(url)(state)) } } - return update; + return update } }, async createGiftUrl() { - const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(initialProps.article.id); + const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(initialProps.article.id) if (redemptionUrl) { - const { url, isShortened } = await api.getShorterUrl(redemptionUrl); - tracking.createGiftLink(url, redemptionUrl); + const { url, isShortened } = await api.getShorterUrl(redemptionUrl) + tracking.createGiftLink(url, redemptionUrl) - return updaters.setGiftUrl(url, redemptionLimit, isShortened); + return updaters.setGiftUrl(url, redemptionLimit, isShortened) } else { // TODO do something } }, copyGiftUrl(event) { - copyToClipboard(event); + copyToClipboard(event) - return state => { - const giftUrl = state.urls.gift; - tracking.copyLink('giftLink', giftUrl); + return (state) => { + const giftUrl = state.urls.gift + tracking.copyLink('giftLink', giftUrl) - return { showCopyConfirmation: true }; - }; + return { showCopyConfirmation: true } + } }, copyNonGiftUrl(event) { - copyToClipboard(event); + copyToClipboard(event) - return state => { - const nonGiftUrl = state.urls.nonGift; - tracking.copyLink('nonGiftLink', nonGiftUrl); + return (state) => { + const nonGiftUrl = state.urls.nonGift + tracking.copyLink('nonGiftLink', nonGiftUrl) - return { showCopyConfirmation: true }; + return { showCopyConfirmation: true } } }, emailGiftUrl() { - return state => { - tracking.emailLink('giftLink', state.urls.gift); - }; + return (state) => { + tracking.emailLink('giftLink', state.urls.gift) + } }, emailNonGiftUrl() { - return state => { - tracking.emailLink('nonGiftLink', state.urls.nonGift); - }; + return (state) => { + tracking.emailLink('nonGiftLink', state.urls.nonGift) + } }, hideCopyConfirmation() { - return { showCopyConfirmation: false }; + return { showCopyConfirmation: false } }, shareByNativeShare() { - throw new Error(`shareByNativeShare should be implemented by x-gift-article's consumers`); + throw new Error(`shareByNativeShare should be implemented by x-gift-article's consumers`) }, activate() { - return async state => { + return async (state) => { if (initialProps.isFreeArticle) { - const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift); + const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift) if (isShortened) { - return updaters.setShortenedNonGiftUrl(url)(state); + return updaters.setShortenedNonGiftUrl(url)(state) } } else { - const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance(); + const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance() // avoid to use giftCredits >= 0 because it returns true when null and "" if (giftCredits > 0 || giftCredits === 0) { - return updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate); + return updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate) } else { - return { invalidResponseFromApi: true }; + return { invalidResponseFromApi: true } } } } } } }, - props => { + (props) => { const initialState = { title: 'Share this article', giftCredits: undefined, @@ -144,27 +140,35 @@ const withGiftFormActions = withActions( mobileShareLinks: props.showMobileShareLinks ? { - facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent(props.article.url)}&t=${encodeURIComponent(props.article.title)}`, - twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(props.article.url)}&text=${encodeURIComponent(props.article.title)}&via=financialtimes`, - linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(props.article.url)}&title=${encodeURIComponent(props.article.title)}&source=Financial+Times`, - whatsapp: `whatsapp://send?text=${encodeURIComponent(props.article.title)}%20-%20${encodeURIComponent(props.article.url)}` - } + facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent( + props.article.url + )}&t=${encodeURIComponent(props.article.title)}`, + twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent( + props.article.url + )}&text=${encodeURIComponent(props.article.title)}&via=financialtimes`, + linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent( + props.article.url + )}&title=${encodeURIComponent(props.article.title)}&source=Financial+Times`, + whatsapp: `whatsapp://send?text=${encodeURIComponent( + props.article.title + )}%20-%20${encodeURIComponent(props.article.url)}` + } : undefined - }; + } - const expandedProps = Object.assign({}, props, initialState); + const expandedProps = Object.assign({}, props, initialState) const sectionProps = props.isFreeArticle ? updaters.showNonGiftUrlSection(expandedProps) - : updaters.showGiftUrlSection(expandedProps); + : updaters.showGiftUrlSection(expandedProps) - return Object.assign(initialState, sectionProps); + return Object.assign(initialState, sectionProps) } -); +) const BaseGiftArticle = (props) => { - return props.isLoading ? <Loading/> : <Form {...props}/>; -}; + return props.isLoading ? <Loading /> : <Form {...props} /> +} -const GiftArticle = withGiftFormActions(BaseGiftArticle); +const GiftArticle = withGiftFormActions(BaseGiftArticle) -export { GiftArticle }; +export { GiftArticle } diff --git a/components/x-gift-article/src/Loading.jsx b/components/x-gift-article/src/Loading.jsx index 572d65d1c..423a987a2 100644 --- a/components/x-gift-article/src/Loading.jsx +++ b/components/x-gift-article/src/Loading.jsx @@ -1,8 +1,8 @@ -import { h } from '@financial-times/x-engine'; -import styles from './GiftArticle.scss'; +import { h } from '@financial-times/x-engine' +import styles from './GiftArticle.scss' export default () => ( - <div className={ styles['loading-spinner__container'] }> - <div className={ styles['loading-spinner']} ></div> + <div className={styles['loading-spinner__container']}> + <div className={styles['loading-spinner']}></div> </div> -); +) diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index c75828018..7c51b39f5 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -1,58 +1,62 @@ -import { h } from '@financial-times/x-engine'; -import { ShareType } from './lib/constants'; -import styles from './GiftArticle.scss'; - -const messageClassName = styles.message; - -export default ({ shareType, isGiftUrlCreated, isFreeArticle, giftCredits, monthlyAllowance, nextRenewalDateText, redemptionLimit, invalidResponseFromApi }) => { - +import { h } from '@financial-times/x-engine' +import { ShareType } from './lib/constants' +import styles from './GiftArticle.scss' + +const messageClassName = styles.message + +export default ({ + shareType, + isGiftUrlCreated, + isFreeArticle, + giftCredits, + monthlyAllowance, + nextRenewalDateText, + redemptionLimit, + invalidResponseFromApi +}) => { if (isFreeArticle) { return ( - <div className={ messageClassName }> + <div className={messageClassName}> This article is currently <strong>free</strong> for anyone to read </div> - ); + ) } if (shareType === ShareType.gift) { if (giftCredits === 0) { return ( - <div className={ messageClassName }> - You’ve used all your <strong>gift article credits</strong><br /> - You’ll get your next { monthlyAllowance } on <strong>{ nextRenewalDateText }</strong> + <div className={messageClassName}> + You’ve used all your <strong>gift article credits</strong> + <br /> + You’ll get your next {monthlyAllowance} on <strong>{nextRenewalDateText}</strong> </div> - ); + ) } if (isGiftUrlCreated) { return ( - <div className={ messageClassName }> + <div className={messageClassName}> This link can be opened up to {redemptionLimit} times and is valid for 90 days </div> - ); + ) } if (invalidResponseFromApi) { - return ( - <div className={ messageClassName }> - Unable to fetch gift credits. Please try again later - </div> - ); + return <div className={messageClassName}>Unable to fetch gift credits. Please try again later</div> } return ( - <div className={ messageClassName }> - You have <strong>{ giftCredits } gift article { giftCredits === 1 ? 'credit' : 'credits' }</strong> left this month + <div className={messageClassName}> + You have{' '} + <strong> + {giftCredits} gift article {giftCredits === 1 ? 'credit' : 'credits'} + </strong>{' '} + left this month </div> - ); + ) } if (shareType === ShareType.nonGift) { - return ( - <div className={ messageClassName }> - This link can only be read by existing subscribers - </div> - ); + return <div className={messageClassName}>This link can only be read by existing subscribers</div> } - -}; +} diff --git a/components/x-gift-article/src/MobileShareButtons.jsx b/components/x-gift-article/src/MobileShareButtons.jsx index f6806fc36..0a010edb1 100644 --- a/components/x-gift-article/src/MobileShareButtons.jsx +++ b/components/x-gift-article/src/MobileShareButtons.jsx @@ -1,32 +1,44 @@ -import { h } from '@financial-times/x-engine'; -import Title from './Title'; +import { h } from '@financial-times/x-engine' +import Title from './Title' -import styles from './MobileShareButtons.scss'; +import styles from './MobileShareButtons.scss' export default ({ mobileShareLinks }) => ( - <div className={ styles.container }> - <Title title={ 'Share on Social' }/> - <div className={ styles["container-inner"] }> - <span className={ styles.button } data-share="facebook"> - <a className={ styles.facebook } rel="noopener" href={ mobileShareLinks.facebook } data-trackable="facebook"> - Facebook <span className={ styles["hidden-button-text"] }>(opens new window)</span> + <div className={styles.container}> + <Title title={'Share on Social'} /> + <div className={styles['container-inner']}> + <span className={styles.button} data-share="facebook"> + <a + className={styles.facebook} + rel="noopener" + href={mobileShareLinks.facebook} + data-trackable="facebook"> + Facebook <span className={styles['hidden-button-text']}>(opens new window)</span> </a> </span> - <span className={ styles.button } data-share="twitter"> - <a className={ styles.twitter } rel="noopener" href={ mobileShareLinks.twitter } data-trackable="twitter"> - Twitter <span className={ styles["hidden-button-text"] }>(opens new window)</span> + <span className={styles.button} data-share="twitter"> + <a className={styles.twitter} rel="noopener" href={mobileShareLinks.twitter} data-trackable="twitter"> + Twitter <span className={styles['hidden-button-text']}>(opens new window)</span> </a> </span> - <span className={ styles.button } data-share="linkedin"> - <a className={ styles.linkedin } rel="noopener" href={ mobileShareLinks.linkedin } data-trackable="linkedin"> - LinkedIn <span className={ styles["hidden-button-text"] }>(opens new window)</span> + <span className={styles.button} data-share="linkedin"> + <a + className={styles.linkedin} + rel="noopener" + href={mobileShareLinks.linkedin} + data-trackable="linkedin"> + LinkedIn <span className={styles['hidden-button-text']}>(opens new window)</span> </a> </span> - <span className={ styles.button } data-share="whatsapp"> - <a className={ styles.whatsapp } rel="noopener" href={ mobileShareLinks.whatsapp } data-trackable="whatsapp"> - Whatsapp <span className={ styles["hidden-button-text"] }>(opens new window)</span> + <span className={styles.button} data-share="whatsapp"> + <a + className={styles.whatsapp} + rel="noopener" + href={mobileShareLinks.whatsapp} + data-trackable="whatsapp"> + Whatsapp <span className={styles['hidden-button-text']}>(opens new window)</span> </a> </span> </div> </div> -); +) diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index d6e98b732..9d61d1427 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -1,6 +1,6 @@ -import { h } from '@financial-times/x-engine'; -import { ShareType } from './lib/constants'; -import styles from './GiftArticle.scss'; +import { h } from '@financial-times/x-engine' +import { ShareType } from './lib/constants' +import styles from './GiftArticle.scss' const radioSectionClassNames = [ styles['o-forms-input'], @@ -8,21 +8,23 @@ const radioSectionClassNames = [ styles['o-forms-input--inline'], styles['o-forms-field'], styles['radio-button-section'] -].join(' '); +].join(' ') export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( - <div className={ radioSectionClassNames } role="group" aria-labelledby="article-share-options"> - <span className={ styles["share-option-title"] } id="article-share-options">Article share options</span> + <div className={radioSectionClassNames} role="group" aria-labelledby="article-share-options"> + <span className={styles['share-option-title']} id="article-share-options"> + Article share options + </span> <label htmlFor="giftLink"> <input type="radio" name="gift-form__radio" value="giftLink" id="giftLink" - checked={ shareType === ShareType.gift } - onChange={ showGiftUrlSection } + checked={shareType === ShareType.gift} + onChange={showGiftUrlSection} /> - <span className={ styles["o-forms-input__label"] }> + <span className={styles['o-forms-input__label']}> with <strong>anyone</strong> (uses 1 gift credit) </span> </label> @@ -33,12 +35,12 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( name="gift-form__radio" value="nonGiftLink" id="nonGiftLink" - checked={ shareType === ShareType.nonGift } - onChange={ showNonGiftUrlSection }/> - <span className={ styles["o-forms-input__label"] }> + checked={shareType === ShareType.nonGift} + onChange={showNonGiftUrlSection} + /> + <span className={styles['o-forms-input__label']}> with <strong>other FT subscribers</strong> </span> </label> - </div> -); +) diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx index 592263a77..f2b468025 100644 --- a/components/x-gift-article/src/Title.jsx +++ b/components/x-gift-article/src/Title.jsx @@ -1,10 +1,10 @@ -import { h } from '@financial-times/x-engine'; -import styles from './GiftArticle.scss'; +import { h } from '@financial-times/x-engine' +import styles from './GiftArticle.scss' -const titleClassNames = [ - styles.title -].join(' '); +const titleClassNames = [styles.title].join(' ') export default ({ title }) => ( - <div className={ titleClassNames } id="gift-article-title">{ title }</div> -); + <div className={titleClassNames} id="gift-article-title"> + {title} + </div> +) diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index b51326903..9b83e5284 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -1,29 +1,23 @@ -import { h } from '@financial-times/x-engine'; -import { ShareType } from './lib/constants'; -import styles from './GiftArticle.scss'; +import { h } from '@financial-times/x-engine' +import { ShareType } from './lib/constants' +import styles from './GiftArticle.scss' +const urlWrapperClassNames = [styles['o-forms-input'], styles['o-forms-input--text']].join(' ') -const urlWrapperClassNames = [ - styles['o-forms-input'], - styles['o-forms-input--text'] -].join(' '); - -const urlClassNames = [ - styles['url-input'] -].join(' '); +const urlClassNames = [styles['url-input']].join(' ') export default ({ shareType, isGiftUrlCreated, url, urlType }) => { return ( - <span className={ urlWrapperClassNames }> + <span className={urlWrapperClassNames}> <input type="text" - name={ urlType } - value={ url } - className={ urlClassNames } - disabled={ shareType === ShareType.gift && !isGiftUrlCreated } + name={urlType} + value={url} + className={urlClassNames} + disabled={shareType === ShareType.gift && !isGiftUrlCreated} readOnly aria-label="Gift article shareable link" /> </span> - ); -}; + ) +} diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 195b7a295..476999df3 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -1,14 +1,11 @@ -import { h } from '@financial-times/x-engine'; -import { ShareType } from './lib/constants'; -import Url from './Url'; -import Message from './Message'; -import Buttons from './Buttons'; -import styles from './GiftArticle.scss'; +import { h } from '@financial-times/x-engine' +import { ShareType } from './lib/constants' +import Url from './Url' +import Message from './Message' +import Buttons from './Buttons' +import styles from './GiftArticle.scss' -const urlSectionClassNames = [ - 'js-gift-article__url-section', - styles['url-section'] -].join(' '); +const urlSectionClassNames = ['js-gift-article__url-section', styles['url-section']].join(' ') export default ({ shareType, @@ -26,44 +23,51 @@ export default ({ invalidResponseFromApi, actions }) => { - - const hideUrlShareElements = ( giftCredits === 0 && shareType === ShareType.gift ); - const showUrlShareElements = !hideUrlShareElements; + const hideUrlShareElements = giftCredits === 0 && shareType === ShareType.gift + const showUrlShareElements = !hideUrlShareElements return ( <div - className={ urlSectionClassNames } - data-section-id={ shareType + 'Link' } - data-trackable={ shareType + 'Link' }> - - { showUrlShareElements && <Url {...{ - shareType, - isGiftUrlCreated, - url, - urlType, - }} /> } - - <Message {...{ - shareType, - isGiftUrlCreated, - isFreeArticle, - giftCredits, - monthlyAllowance, - nextRenewalDateText, - redemptionLimit, - invalidResponseFromApi, - }} /> + className={urlSectionClassNames} + data-section-id={shareType + 'Link'} + data-trackable={shareType + 'Link'}> + {showUrlShareElements && ( + <Url + {...{ + shareType, + isGiftUrlCreated, + url, + urlType + }} + /> + )} - { showUrlShareElements && <Buttons {...{ - shareType, - isGiftUrlCreated, - mailtoUrl, - showCopyButton, - nativeShare, - actions, - giftCredits - }} /> } + <Message + {...{ + shareType, + isGiftUrlCreated, + isFreeArticle, + giftCredits, + monthlyAllowance, + nextRenewalDateText, + redemptionLimit, + invalidResponseFromApi + }} + /> + {showUrlShareElements && ( + <Buttons + {...{ + shareType, + isGiftUrlCreated, + mailtoUrl, + showCopyButton, + nativeShare, + actions, + giftCredits + }} + /> + )} </div> - ); -}; + ) +} diff --git a/components/x-gift-article/src/lib/api.js b/components/x-gift-article/src/lib/api.js index 0b5594660..04e399066 100644 --- a/components/x-gift-article/src/lib/api.js +++ b/components/x-gift-article/src/lib/api.js @@ -1,76 +1,79 @@ export default class ApiClient { - constructor ({ protocol, domain } = {}) { - this.protocol = protocol; - this.domain = domain; + constructor({ protocol, domain } = {}) { + this.protocol = protocol + this.domain = domain } getFetchUrl(path) { - let base = ''; + let base = '' if (this.domain) { - base = `//${this.domain}`; + base = `//${this.domain}` if (this.protocol) { - base = `${this.protocol}:${base}`; + base = `${this.protocol}:${base}` } } - return `${base}${path}`; + return `${base}${path}` } fetchJson(path, additionalOptions) { - const url = this.getFetchUrl(path); - const options = Object.assign({ - credentials: 'include' - }, additionalOptions); + const url = this.getFetchUrl(path) + const options = Object.assign( + { + credentials: 'include' + }, + additionalOptions + ) - return fetch(url, options).then(response => response.json()); + return fetch(url, options).then((response) => response.json()) } async getGiftArticleAllowance() { try { - const json = await this.fetchJson('/article/gift-credits'); + const json = await this.fetchJson('/article/gift-credits') return { monthlyAllowance: json.allowance, giftCredits: json.remainingCredits, nextRenewalDate: json.renewalDate - }; + } } catch (e) { - return { monthlyAllowance: undefined, giftCredits: undefined, nextRenewalDate: undefined }; + return { monthlyAllowance: undefined, giftCredits: undefined, nextRenewalDate: undefined } } } async getGiftUrl(articleId) { try { - const json = await this.fetchJson('/article/gift-link/' + encodeURIComponent(articleId)); + const json = await this.fetchJson('/article/gift-link/' + encodeURIComponent(articleId)) if (json.errors) { - throw new Error(`Failed to get gift article link: ${json.errors.join(', ')}`); + throw new Error(`Failed to get gift article link: ${json.errors.join(', ')}`) } return { ...json - }; + } } catch (e) { - return { redemptionUrl: undefined, redemptionLimit: undefined }; + return { redemptionUrl: undefined, redemptionLimit: undefined } } } async getShorterUrl(originalUrl) { - let url = originalUrl; - let isShortened = false; + let url = originalUrl + let isShortened = false try { - const json = await this.fetchJson('/article/shorten-url/' + encodeURIComponent(originalUrl)); + const json = await this.fetchJson('/article/shorten-url/' + encodeURIComponent(originalUrl)) if (json.shortenedUrl) { - isShortened = true; - url = json.shortenedUrl; + isShortened = true + url = json.shortenedUrl } } catch (e) { // do nothing because it just returns original url at the end } - return { url, isShortened }; + return { url, isShortened } } } diff --git a/components/x-gift-article/src/lib/constants.js b/components/x-gift-article/src/lib/constants.js index dc8ff8d49..792b42a11 100644 --- a/components/x-gift-article/src/lib/constants.js +++ b/components/x-gift-article/src/lib/constants.js @@ -1,10 +1,10 @@ export const ShareType = { gift: 'gift', nonGift: 'nonGift' -}; +} export const UrlType = { dummy: 'example-gift-link', gift: 'gift-link', nonGift: 'non-gift-link' -}; +} diff --git a/components/x-gift-article/src/lib/share-link-actions.js b/components/x-gift-article/src/lib/share-link-actions.js index 0992efc5b..d39ddb3fb 100644 --- a/components/x-gift-article/src/lib/share-link-actions.js +++ b/components/x-gift-article/src/lib/share-link-actions.js @@ -1,37 +1,35 @@ -function createMailtoUrl (articleTitle, shareUrl) { - const subject = encodeURIComponent(articleTitle); - const body = encodeURIComponent(shareUrl); +function createMailtoUrl(articleTitle, shareUrl) { + const subject = encodeURIComponent(articleTitle) + const body = encodeURIComponent(shareUrl) - return `mailto:?subject=${subject}&body=${body}`; + return `mailto:?subject=${subject}&body=${body}` } +function copyToClipboard(event) { + const urlSection = event.target.closest('.js-gift-article__url-section') + const inputEl = urlSection.querySelector('input') + const oldContentEditable = inputEl.contentEditable + const oldReadOnly = inputEl.readOnly + const range = document.createRange() -function copyToClipboard (event) { + inputEl.contenteditable = true + inputEl.readonly = false + inputEl.focus() + range.selectNodeContents(inputEl) - const urlSection = event.target.closest('.js-gift-article__url-section'); - const inputEl = urlSection.querySelector('input'); - const oldContentEditable = inputEl.contentEditable; - const oldReadOnly = inputEl.readOnly; - const range = document.createRange(); - - inputEl.contenteditable = true; - inputEl.readonly = false; - inputEl.focus(); - range.selectNodeContents(inputEl); - - const selection = window.getSelection(); + const selection = window.getSelection() try { - selection.removeAllRanges(); - selection.addRange(range); - inputEl.setSelectionRange(0, 999999); + selection.removeAllRanges() + selection.addRange(range) + inputEl.setSelectionRange(0, 999999) } catch (err) { - inputEl.select(); // IE11 etc. + inputEl.select() // IE11 etc. } - inputEl.contentEditable = oldContentEditable; - inputEl.readOnly = oldReadOnly; - document.execCommand('copy'); - inputEl.blur(); + inputEl.contentEditable = oldContentEditable + inputEl.readOnly = oldReadOnly + document.execCommand('copy') + inputEl.blur() } module.exports = { diff --git a/components/x-gift-article/src/lib/tracking.js b/components/x-gift-article/src/lib/tracking.js index 4575d50a4..302fbad00 100644 --- a/components/x-gift-article/src/lib/tracking.js +++ b/components/x-gift-article/src/lib/tracking.js @@ -1,34 +1,35 @@ -function dispatchEvent (detail) { +function dispatchEvent(detail) { const event = new CustomEvent('oTracking.event', { detail, bubbles: true - }); + }) - document.body.dispatchEvent(event); + document.body.dispatchEvent(event) } module.exports = { + createGiftLink: (link, longUrl) => + dispatchEvent({ + category: 'gift-link', + action: 'create', + linkType: 'giftLink', + link, + longUrl + }), - createGiftLink: (link, longUrl) => dispatchEvent({ - category: 'gift-link', - action: 'create', - linkType: 'giftLink', - link, - longUrl - }), - - copyLink: (linkType, link) => dispatchEvent({ - category: 'gift-link', - action: 'copy', - linkType, - link - }), + copyLink: (linkType, link) => + dispatchEvent({ + category: 'gift-link', + action: 'copy', + linkType, + link + }), - emailLink: (linkType, link) => dispatchEvent({ - category: 'gift-link', - action: 'mailto', - linkType, - link - }) - -}; + emailLink: (linkType, link) => + dispatchEvent({ + category: 'gift-link', + action: 'mailto', + linkType, + link + }) +} diff --git a/components/x-gift-article/src/lib/updaters.js b/components/x-gift-article/src/lib/updaters.js index f50d1113b..4975c2dcd 100644 --- a/components/x-gift-article/src/lib/updaters.js +++ b/components/x-gift-article/src/lib/updaters.js @@ -1,7 +1,20 @@ -import { createMailtoUrl } from './share-link-actions'; -import { ShareType, UrlType } from './constants'; +import { createMailtoUrl } from './share-link-actions' +import { ShareType, UrlType } from './constants' -const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; +const monthNames = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' +] export const showGiftUrlSection = (props) => ({ shareType: ShareType.gift, @@ -9,7 +22,7 @@ export const showGiftUrlSection = (props) => ({ urlType: props.urls.gift ? UrlType.gift : UrlType.dummy, mailtoUrl: props.mailtoUrls.gift, showCopyConfirmation: false -}); +}) export const showNonGiftUrlSection = (props) => ({ shareType: ShareType.nonGift, @@ -17,10 +30,10 @@ export const showNonGiftUrlSection = (props) => ({ urlType: UrlType.nonGift, mailtoUrl: props.mailtoUrls.nonGift, showCopyConfirmation: false -}); +}) -export const setGiftUrl = (url, redemptionLimit, isShortened) => props => { - const mailtoUrl = createMailtoUrl(props.article.title, url); +export const setGiftUrl = (url, redemptionLimit, isShortened) => (props) => { + const mailtoUrl = createMailtoUrl(props.article.title, url) return { url, @@ -31,29 +44,29 @@ export const setGiftUrl = (url, redemptionLimit, isShortened) => props => { urlType: UrlType.gift, urls: Object.assign(props.urls, { - gift: url, + gift: url }), mailtoUrls: Object.assign(props.mailtoUrls, { - gift: mailtoUrl, + gift: mailtoUrl }) - }; -}; + } +} export const setAllowance = (giftCredits, monthlyAllowance, nextRenewalDate) => { - const date = new Date(nextRenewalDate); - const nextRenewalDateText = `${ monthNames[date.getMonth()] } ${ date.getDate() }`; + const date = new Date(nextRenewalDate) + const nextRenewalDateText = `${monthNames[date.getMonth()]} ${date.getDate()}` return { giftCredits, monthlyAllowance, nextRenewalDateText, invalidResponseFromApi: false - }; -}; + } +} -export const setShortenedNonGiftUrl = (shortenedUrl) => props => { - const mailtoUrl = createMailtoUrl(props.article.title, shortenedUrl); +export const setShortenedNonGiftUrl = (shortenedUrl) => (props) => { + const mailtoUrl = createMailtoUrl(props.article.title, shortenedUrl) return { url: shortenedUrl, @@ -61,11 +74,11 @@ export const setShortenedNonGiftUrl = (shortenedUrl) => props => { isNonGiftUrlShortened: true, urls: Object.assign(props.urls, { - gift: shortenedUrl, + gift: shortenedUrl }), mailtoUrls: Object.assign(props.mailtoUrls, { - gift: mailtoUrl, + gift: mailtoUrl }) - }; -}; + } +} diff --git a/components/x-gift-article/stories/error-response.js b/components/x-gift-article/stories/error-response.js index c7e96547b..190b8879d 100644 --- a/components/x-gift-article/stories/error-response.js +++ b/components/x-gift-article/stories/error-response.js @@ -1,7 +1,7 @@ -const articleUrl = 'https://www.ft.com/content/blahblahblah'; -const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'With a bad response from membership APIs'; +exports.title = 'With a bad response from membership APIs' exports.data = { title: 'Share this article (unable to fetch credits)', @@ -9,24 +9,20 @@ exports.data = { article: { id: 'article id', url: articleUrl, - title: 'Title Title Title Title', - }, -}; + title: 'Title Title Title Title' + } +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module -exports.fetchMock = fetchMock => { +exports.fetchMock = (fetchMock) => { fetchMock - .get( - '/article/gift-credits', - { - throw: new Error('bad membership api') - } - ) - .get( - `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, - { shortenedUrl: 'https://shortened-non-gift-url' } - ); -}; + .get('/article/gift-credits', { + throw: new Error('bad membership api') + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) +} diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/stories/free-article.js index 9fa3b8452..e80ca5069 100644 --- a/components/x-gift-article/stories/free-article.js +++ b/components/x-gift-article/stories/free-article.js @@ -1,7 +1,7 @@ -const articleUrl = 'https://www.ft.com/content/blahblahblah'; -const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'Free article'; +exports.title = 'Free article' exports.data = { title: 'Share this article (free)', @@ -9,29 +9,25 @@ exports.data = { article: { title: 'Title Title Title Title', id: 'base-gift-article-static-id', - url: articleUrl, - }, -}; + url: articleUrl + } +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module -exports.fetchMock = fetchMock => { +exports.fetchMock = (fetchMock) => { fetchMock - .get( - '/article/gift-credits', - { 'credits': - { - 'allowance': 20, - 'consumedCredits': 5, - 'remainingCredits': 15, - 'renewalDate': '2018-08-01T00:00:00Z' - } + .get('/article/gift-credits', { + credits: { + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' } - ) - .get( - `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, - { shortenedUrl: 'https://shortened-non-gift-url' } - ); -}; + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) +} diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js index 375852fea..cdaf47d26 100644 --- a/components/x-gift-article/stories/index.js +++ b/components/x-gift-article/stories/index.js @@ -1,12 +1,12 @@ -const { GiftArticle } = require('../'); +const { GiftArticle } = require('../') -exports.component = GiftArticle; +exports.component = GiftArticle -exports.package = require('../package.json'); +exports.package = require('../package.json') exports.dependencies = { 'o-fonts': '^3.0.0' -}; +} exports.stories = [ require('./with-gift-credits'), @@ -15,4 +15,4 @@ exports.stories = [ require('./free-article'), require('./native-share'), require('./error-response') -]; +] diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/stories/native-share.js index 210ec5edb..2c8ca9030 100644 --- a/components/x-gift-article/stories/native-share.js +++ b/components/x-gift-article/stories/native-share.js @@ -1,9 +1,9 @@ -const articleId = 'article id'; -const articleUrl = 'https://www.ft.com/content/blahblahblah'; -const articleUrlRedeemed = 'https://gift-url-redeemed'; -const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; +const articleId = 'article id' +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const articleUrlRedeemed = 'https://gift-url-redeemed' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'With native share on App'; +exports.title = 'With native share on App' exports.data = { title: 'Share this article (on App)', @@ -11,42 +11,34 @@ exports.data = { article: { id: articleId, url: articleUrl, - title: 'Title Title Title Title', + title: 'Title Title Title Title' }, nativeShare: true, id: 'base-gift-article-static-id' -}; +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module -exports.fetchMock = fetchMock => { +exports.fetchMock = (fetchMock) => { fetchMock - .get( - '/article/gift-credits', - { 'credits': - { - 'allowance': 20, - 'consumedCredits': 2, - 'remainingCredits': 18, - 'renewalDate': '2018-08-01T00:00:00Z' - } + .get('/article/gift-credits', { + credits: { + allowance: 20, + consumedCredits: 2, + remainingCredits: 18, + renewalDate: '2018-08-01T00:00:00Z' } - ) - .get( - `/article/shorten-url/${ encodeURIComponent(articleUrlRedeemed) }`, - { shortenedUrl: 'https://shortened-gift-url' } - ) - .get( - `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, - { shortenedUrl: 'https://shortened-non-gift-url' } - ) - .get( - `/article/gift-link/${ encodeURIComponent(articleId) }`, - { - 'redemptionUrl': articleUrlRedeemed, - 'remainingAllowance': 1 - } - ); -}; + }) + .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { + shortenedUrl: 'https://shortened-gift-url' + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) + .get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + redemptionUrl: articleUrlRedeemed, + remainingAllowance: 1 + }) +} diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/stories/with-gift-credits.js index 01292997e..7dc43e6a6 100644 --- a/components/x-gift-article/stories/with-gift-credits.js +++ b/components/x-gift-article/stories/with-gift-credits.js @@ -1,9 +1,9 @@ -const articleId = 'article id'; -const articleUrl = 'https://www.ft.com/content/blahblahblah'; -const articleUrlRedeemed = 'https://gift-url-redeemed'; -const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; +const articleId = 'article id' +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const articleUrlRedeemed = 'https://gift-url-redeemed' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'With gift credits'; +exports.title = 'With gift credits' exports.data = { title: 'Share this article (with credit)', @@ -11,42 +11,34 @@ exports.data = { article: { id: articleId, url: articleUrl, - title: 'Title Title Title Title', + title: 'Title Title Title Title' }, showMobileShareLinks: true, id: 'base-gift-article-static-id' -}; +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module -exports.fetchMock = fetchMock => { +exports.fetchMock = (fetchMock) => { fetchMock - .get( - '/article/gift-credits', - { 'credits': - { - 'allowance': 20, - 'consumedCredits': 5, - 'remainingCredits': 15, - 'renewalDate': '2018-08-01T00:00:00Z' - } + .get('/article/gift-credits', { + credits: { + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' } - ) - .get( - `/article/shorten-url/${ encodeURIComponent(articleUrlRedeemed) }`, - { shortenedUrl: 'https://shortened-gift-url' } - ) - .get( - `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, - { shortenedUrl: 'https://shortened-non-gift-url' } - ) - .get( - `/article/gift-link/${ encodeURIComponent(articleId) }`, - { - 'redemptionUrl': articleUrlRedeemed, - 'remainingAllowance': 1 - } - ); -}; + }) + .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { + shortenedUrl: 'https://shortened-gift-url' + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) + .get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + redemptionUrl: articleUrlRedeemed, + remainingAllowance: 1 + }) +} diff --git a/components/x-gift-article/stories/with-gift-link.js b/components/x-gift-article/stories/with-gift-link.js index 63cf5732d..a5c279197 100644 --- a/components/x-gift-article/stories/with-gift-link.js +++ b/components/x-gift-article/stories/with-gift-link.js @@ -1,8 +1,8 @@ -const articleId = 'article id'; -const articleUrl = 'https://www.ft.com/content/blahblahblah'; -const articleUrlRedeemed = 'https://gift-url-redeemed'; +const articleId = 'article id' +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const articleUrlRedeemed = 'https://gift-url-redeemed' -exports.title = 'With gift link'; +exports.title = 'With gift link' exports.data = { title: 'Share this article (with gift link)', @@ -12,23 +12,19 @@ exports.data = { article: { id: articleId, url: articleUrl, - title: 'Title Title Title Title', + title: 'Title Title Title Title' }, showMobileShareLinks: true, id: 'base-gift-article-static-id' -}; +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module -exports.fetchMock = fetchMock => { - fetchMock - .get( - `/article/gift-link/${ encodeURIComponent(articleId) }`, - { - 'redemptionUrl': articleUrlRedeemed, - 'remainingAllowance': 1 - } - ); -}; +exports.fetchMock = (fetchMock) => { + fetchMock.get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + redemptionUrl: articleUrlRedeemed, + remainingAllowance: 1 + }) +} diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/stories/without-gift-credits.js index 494c76040..4eb2226f4 100644 --- a/components/x-gift-article/stories/without-gift-credits.js +++ b/components/x-gift-article/stories/without-gift-credits.js @@ -1,7 +1,7 @@ -const articleUrl = 'https://www.ft.com/content/blahblahblah'; -const nonGiftArticleUrl = `${articleUrl}?shareType=nongift`; +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'Without gift credits'; +exports.title = 'Without gift credits' exports.data = { title: 'Share this article (without credit)', @@ -9,29 +9,25 @@ exports.data = { article: { id: 'article id', url: articleUrl, - title: 'Title Title Title Title', - }, -}; + title: 'Title Title Title Title' + } +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module -exports.fetchMock = fetchMock => { +exports.fetchMock = (fetchMock) => { fetchMock - .get( - '/article/gift-credits', - { 'credits': - { - 'allowance': 20, - 'consumedCredits': 20, - 'remainingCredits': 0, - 'renewalDate': '2018-08-01T00:00:00Z' - } + .get('/article/gift-credits', { + credits: { + allowance: 20, + consumedCredits: 20, + remainingCredits: 0, + renewalDate: '2018-08-01T00:00:00Z' } - ) - .get( - `/article/shorten-url/${ encodeURIComponent(nonGiftArticleUrl) }`, - { shortenedUrl: 'https://shortened-non-gift-url' } - ); -}; + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) +} diff --git a/components/x-increment/__tests__/x-increment.test.jsx b/components/x-increment/__tests__/x-increment.test.jsx index 544f6726b..4ccff4281 100644 --- a/components/x-increment/__tests__/x-increment.test.jsx +++ b/components/x-increment/__tests__/x-increment.test.jsx @@ -1,30 +1,30 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -const { Increment } = require('../'); +const { Increment } = require('../') describe('x-increment', () => { it('should increment when action is triggered', async () => { - const subject = mount(<Increment count={1} />); - await subject.find('BaseIncrement').prop('actions').increment(); + const subject = mount(<Increment count={1} />) + await subject.find('BaseIncrement').prop('actions').increment() - expect(subject.find('span').text()).toEqual('2'); - }); + expect(subject.find('span').text()).toEqual('2') + }) it('should increment by amount from action arg', async () => { - const subject = mount(<Increment count={1} />); - await subject.find('BaseIncrement').prop('actions').increment({ amount: 2 }); + const subject = mount(<Increment count={1} />) + await subject.find('BaseIncrement').prop('actions').increment({ amount: 2 }) - expect(subject.find('span').text()).toEqual('3'); - }); + expect(subject.find('span').text()).toEqual('3') + }) it('should increment when clicked, waiting for timeout', async () => { - const subject = mount(<Increment count={1} timeout={1000} />); - const start = Date.now(); + const subject = mount(<Increment count={1} timeout={1000} />) + const start = Date.now() - await subject.find('button').prop('onClick')(); + await subject.find('button').prop('onClick')() - expect(Date.now() - start).toBeCloseTo(1000, -2); // negative precision ⇒ left of decimal point - expect(subject.find('span').text()).toEqual('2'); - }); -}); + expect(Date.now() - start).toBeCloseTo(1000, -2) // negative precision ⇒ left of decimal point + expect(subject.find('span').text()).toEqual('2') + }) +}) diff --git a/components/x-increment/rollup.js b/components/x-increment/rollup.js index e29c5583b..a2312ca80 100644 --- a/components/x-increment/rollup.js +++ b/components/x-increment/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/Increment.jsx', pkg }); +xRollup({ input: './src/Increment.jsx', pkg }) diff --git a/components/x-increment/src/Increment.jsx b/components/x-increment/src/Increment.jsx index 5b91f7c10..d3aa1a462 100644 --- a/components/x-increment/src/Increment.jsx +++ b/components/x-increment/src/Increment.jsx @@ -1,28 +1,27 @@ -import { h } from '@financial-times/x-engine'; -import { withActions } from '@financial-times/x-interaction'; +import { h } from '@financial-times/x-engine' +import { withActions } from '@financial-times/x-interaction' -const delay = ms => new Promise(r => setTimeout(r, ms)); +const delay = (ms) => new Promise((r) => setTimeout(r, ms)) -const withIncrementActions = withActions(({timeout}) => ({ +const withIncrementActions = withActions(({ timeout }) => ({ async increment({ amount = 1 } = {}) { - await delay(timeout); + await delay(timeout) - return ({count}) => ({ - count: count + amount, - }); - }, -})); + return ({ count }) => ({ + count: count + amount + }) + } +})) -const BaseIncrement = ({count, actions: {increment}, isLoading}) => <div> - <span>{count}</span> - <button onClick={() => increment()} disabled={isLoading}> - {isLoading - ? 'Loading...' - : 'Increment' - } - </button> -</div>; +const BaseIncrement = ({ count, actions: { increment }, isLoading }) => ( + <div> + <span>{count}</span> + <button onClick={() => increment()} disabled={isLoading}> + {isLoading ? 'Loading...' : 'Increment'} + </button> + </div> +) -const Increment = withIncrementActions(BaseIncrement); +const Increment = withIncrementActions(BaseIncrement) -export { Increment }; +export { Increment } diff --git a/components/x-increment/storybook/index.jsx b/components/x-increment/storybook/index.jsx index ae1fd3455..31443a1e3 100644 --- a/components/x-increment/storybook/index.jsx +++ b/components/x-increment/storybook/index.jsx @@ -1,22 +1,22 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { Increment } from '../src/Increment'; +import React from 'react' +import { storiesOf } from '@storybook/react' +import { Increment } from '../src/Increment' storiesOf('x-increment', module) .add('Sync', () => { const data = { count: 1, id: 'base-increment-static-id' - }; + } - return <Increment {...data} />; + return <Increment {...data} /> }) .add('Async', () => { const data = { count: 1, timeout: 1000, id: 'base-increment-static-id' - }; + } - return <Increment {...data} />; - }); + return <Increment {...data} /> + }) diff --git a/components/x-interaction/__tests__/x-interaction.test.jsx b/components/x-interaction/__tests__/x-interaction.test.jsx index 16b842bba..013978a5b 100644 --- a/components/x-interaction/__tests__/x-interaction.test.jsx +++ b/components/x-interaction/__tests__/x-interaction.test.jsx @@ -1,7 +1,7 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -const { withActions } = require('../'); +const { withActions } = require('../') describe('x-interaction', () => { describe('withActions', () => { @@ -9,306 +9,278 @@ describe('x-interaction', () => { const originalProps = { className: 'foo', bax: Math.random(), - [Math.random()]: 'baz', - }; + [Math.random()]: 'baz' + } - const Base = () => null; - const Wrapped = withActions({})(Base); - const target = mount(<Wrapped {...originalProps} />); + const Base = () => null + const Wrapped = withActions({})(Base) + const target = mount(<Wrapped {...originalProps} />) - expect(target.find(Base).props()).toMatchObject(originalProps); - }); + expect(target.find(Base).props()).toMatchObject(originalProps) + }) it('should add actions', () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo() {}, - })(Base); + foo() {} + })(Base) - const target = mount(<Wrapped />); - const props = target.find(Base).props(); + const target = mount(<Wrapped />) + const props = target.find(Base).props() - expect(props).toHaveProperty('actions'); - expect(props.actions.foo).toBeInstanceOf(Function); - }); + expect(props).toHaveProperty('actions') + expect(props.actions.foo).toBeInstanceOf(Function) + }) it('should call underlying function when action is called, passing through args', () => { - const foo = jest.fn(); + const foo = jest.fn() - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo, - })(Base); + foo + })(Base) - const target = mount(<Wrapped />); - const props = target.find(Base).props(); + const target = mount(<Wrapped />) + const props = target.find(Base).props() const args = ['bar', 'baz'].concat( // random length args to verify they're all passed through Array(Math.floor(10 * Math.random())).fill('quux') - ); + ) - props.actions.foo(...args); + props.actions.foo(...args) - expect(foo).toHaveBeenLastCalledWith(...args); - }); + expect(foo).toHaveBeenLastCalledWith(...args) + }) it('should return promise from action even when synchronous', () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo() {}, - })(Base); + foo() {} + })(Base) - const target = mount(<Wrapped />); - const props = target.find(Base).props(); + const target = mount(<Wrapped />) + const props = target.find(Base).props() - expect( - props.actions.foo() - ).toBeInstanceOf(Promise); - }); + expect(props.actions.foo()).toBeInstanceOf(Promise) + }) it('should update props of base with return value of action', async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo: () => ({ bar: 10 }), - })(Base); + foo: () => ({ bar: 10 }) + })(Base) - const target = mount(<Wrapped bar={5} />); + const target = mount(<Wrapped bar={5} />) - await target.find(Base).prop('actions').foo(); - target.update(); // tell enzyme things have changed + await target.find(Base).prop('actions').foo() + target.update() // tell enzyme things have changed - expect( - target.find(Base).prop('bar') - ).toBe(10); - }); + expect(target.find(Base).prop('bar')).toBe(10) + }) it('should update props of base using updater function from action', async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo: () => ({bar}) => ({ bar: bar + 5 }), - })(Base); + foo: () => ({ bar }) => ({ bar: bar + 5 }) + })(Base) - const target = mount(<Wrapped bar={5} />); + const target = mount(<Wrapped bar={5} />) - await target.find(Base).prop('actions').foo(); - target.update(); + await target.find(Base).prop('actions').foo() + target.update() - expect( - target.find(Base).prop('bar') - ).toBe(10); - }); + expect(target.find(Base).prop('bar')).toBe(10) + }) it('should update props of base using async updater function from action', async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo: () => async ({bar}) => ({ bar: bar + 5 }), - })(Base); - - const target = mount(<Wrapped bar={5} />); + foo: () => async ({ bar }) => ({ bar: bar + 5 }) + })(Base) - await target.find(Base).prop('actions').foo(); - target.update(); + const target = mount(<Wrapped bar={5} />) - expect( - target.find(Base).prop('bar') - ).toBe(10); - }); + await target.find(Base).prop('actions').foo() + target.update() + expect(target.find(Base).prop('bar')).toBe(10) + }) it('should wait for promises and apply resolved state updates', async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo: () => Promise.resolve({ bar: 10 }), - })(Base); + foo: () => Promise.resolve({ bar: 10 }) + })(Base) - const target = mount(<Wrapped bar={5} />); + const target = mount(<Wrapped bar={5} />) - await target.find(Base).prop('actions').foo(); - target.update(); // tell enzyme things have changed + await target.find(Base).prop('actions').foo() + target.update() // tell enzyme things have changed - expect( - target.find(Base).prop('bar') - ).toBe(10); - }); + expect(target.find(Base).prop('bar')).toBe(10) + }) it('should set isLoading to true while waiting for promises', async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo: () => new Promise(resolve => { - setTimeout(resolve, 200, { bar: 10 }); - }), - })(Base); + foo: () => + new Promise((resolve) => { + setTimeout(resolve, 200, { bar: 10 }) + }) + })(Base) - const target = mount(<Wrapped bar={5} />); - const promise = target.find(Base).prop('actions').foo(); + const target = mount(<Wrapped bar={5} />) + const promise = target.find(Base).prop('actions').foo() - await Promise.resolve(); // wait one microtask - target.update(); + await Promise.resolve() // wait one microtask + target.update() - expect( - target.find(Base).prop('isLoading') - ).toBe(true); + expect(target.find(Base).prop('isLoading')).toBe(true) - await promise; - target.update(); + await promise + target.update() - expect( - target.find(Base).prop('isLoading') - ).toBe(false); - }); + expect(target.find(Base).prop('isLoading')).toBe(false) + }) it(`shouldn't set isLoading back to false until everything is finished`, async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo: () => new Promise(resolve => { - setTimeout(resolve, 200, { bar: 10 }); - }), - })(Base); - - const target = mount(<Wrapped bar={5} />); - const promise1 = target.find(Base).prop('actions').foo(); + foo: () => + new Promise((resolve) => { + setTimeout(resolve, 200, { bar: 10 }) + }) + })(Base) - await new Promise(resolve => { - setTimeout(resolve, 100); - }); + const target = mount(<Wrapped bar={5} />) + const promise1 = target.find(Base).prop('actions').foo() - const promise2 = target.find(Base).prop('actions').foo(); - target.update(); + await new Promise((resolve) => { + setTimeout(resolve, 100) + }) - expect( - target.find(Base).prop('isLoading') - ).toBe(true); + const promise2 = target.find(Base).prop('actions').foo() + target.update() - await promise1; - target.update(); + expect(target.find(Base).prop('isLoading')).toBe(true) - expect( - target.find(Base).prop('isLoading') - ).toBe(true); + await promise1 + target.update() - await promise2; - target.update(); + expect(target.find(Base).prop('isLoading')).toBe(true) - expect( - target.find(Base).prop('isLoading') - ).toBe(false); - }); + await promise2 + target.update() + expect(target.find(Base).prop('isLoading')).toBe(false) + }) it('should update when outside props change but prefer state changes', async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo: () => ({ bar: 15 }), - })(Base); + foo: () => ({ bar: 15 }) + })(Base) - const target = mount(<Wrapped bar={5} />); + const target = mount(<Wrapped bar={5} />) - target.setProps({ bar: 10 }); - target.update(); + target.setProps({ bar: 10 }) + target.update() - expect( - target.find(Base).prop('bar') - ).toBe(10); + expect(target.find(Base).prop('bar')).toBe(10) - await target.find(Base).prop('actions').foo(); - target.update(); + await target.find(Base).prop('actions').foo() + target.update() - expect( - target.find(Base).prop('bar') - ).toBe(15); - }); + expect(target.find(Base).prop('bar')).toBe(15) + }) it('should pass changed outside props to state updaters', async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo: () => ({ bar }) => ({ bar: bar + 5 }), - })(Base); + foo: () => ({ bar }) => ({ bar: bar + 5 }) + })(Base) - const target = mount(<Wrapped bar={5} />); + const target = mount(<Wrapped bar={5} />) - target.setProps({ bar: 10 }); - target.update(); + target.setProps({ bar: 10 }) + target.update() - await target.find(Base).prop('actions').foo(); - target.update(); + await target.find(Base).prop('actions').foo() + target.update() - expect( - target.find(Base).prop('bar') - ).toBe(15); - }); + expect(target.find(Base).prop('bar')).toBe(15) + }) describe('actionsRef', () => { it('should pass actions to actionsRef on mount and null on unmount', async () => { - const actionsRef = jest.fn(); + const actionsRef = jest.fn() - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - foo() {}, - })(Base); + foo() {} + })(Base) - const target = mount(<Wrapped actionsRef={actionsRef} />); + const target = mount(<Wrapped actionsRef={actionsRef} />) - expect(actionsRef).toHaveBeenCalled(); - expect(actionsRef.mock.calls[0][0]).toHaveProperty('foo'); + expect(actionsRef).toHaveBeenCalled() + expect(actionsRef.mock.calls[0][0]).toHaveProperty('foo') - target.unmount(); + target.unmount() - expect(actionsRef).toHaveBeenLastCalledWith(null); - }); + expect(actionsRef).toHaveBeenLastCalledWith(null) + }) it('should pass all actions for rewrapped components', async () => { - const actionsRef = jest.fn(); + const actionsRef = jest.fn() - const Base = () => null; + const Base = () => null const Wrapped = withActions({ - bar() {}, - })(withActions({ - foo() {}, - })(Base)); + bar() {} + })( + withActions({ + foo() {} + })(Base) + ) - mount(<Wrapped actionsRef={actionsRef} />); + mount(<Wrapped actionsRef={actionsRef} />) - expect(actionsRef).toHaveBeenCalled(); - expect(actionsRef.mock.calls[0][0]).toHaveProperty('foo'); - expect(actionsRef.mock.calls[0][0]).toHaveProperty('bar'); - }); - }); + expect(actionsRef).toHaveBeenCalled() + expect(actionsRef.mock.calls[0][0]).toHaveProperty('foo') + expect(actionsRef.mock.calls[0][0]).toHaveProperty('bar') + }) + }) it(`shouldn't reset props when others change`, async () => { - const Base = () => null; + const Base = () => null const Wrapped = withActions({ foo: () => ({ bar: 10 }), - baz: () => ({ quux: 10 }), - })(Base); + baz: () => ({ quux: 10 }) + })(Base) - const target = mount(<Wrapped bar={5} quux={5} />); + const target = mount(<Wrapped bar={5} quux={5} />) - await target.find(Base).prop('actions').foo(); - await target.find(Base).prop('actions').baz(); - target.update(); // tell enzyme things have changed + await target.find(Base).prop('actions').foo() + await target.find(Base).prop('actions').baz() + target.update() // tell enzyme things have changed - expect( - target.find(Base).prop('bar') - ).toBe(10); + expect(target.find(Base).prop('bar')).toBe(10) - expect( - target.find(Base).prop('quux') - ).toBe(10); - }); + expect(target.find(Base).prop('quux')).toBe(10) + }) it('should get default state from second argument', async () => { - const Base = () => null; - const Wrapped = withActions({}, { - foo: 5 - })(Base); - - const target = mount(<Wrapped />); - - expect( - target.find(Base).prop('foo') - ).toBe(5); - }); - - }); -}); + const Base = () => null + const Wrapped = withActions( + {}, + { + foo: 5 + } + )(Base) + + const target = mount(<Wrapped />) + + expect(target.find(Base).prop('foo')).toBe(5) + }) + }) +}) diff --git a/components/x-interaction/rollup.js b/components/x-interaction/rollup.js index 922d28e45..bb83e4f4d 100644 --- a/components/x-interaction/rollup.js +++ b/components/x-interaction/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/Interaction.jsx', pkg }); +xRollup({ input: './src/Interaction.jsx', pkg }) diff --git a/components/x-interaction/src/Hydrate.jsx b/components/x-interaction/src/Hydrate.jsx index 8bd0a0069..064b9b455 100644 --- a/components/x-interaction/src/Hydrate.jsx +++ b/components/x-interaction/src/Hydrate.jsx @@ -1,80 +1,83 @@ -import { h, render, Component } from '@financial-times/x-engine'; -import { getComponent } from './concerns/register-component'; +import { h, render, Component } from '@financial-times/x-engine' +import { getComponent } from './concerns/register-component' export class HydrationWrapper extends Component { render() { - const {Component, props, id} = this.props; - return <Component {...props} id={id} actionsRef={a => this.actions = a} />; + const { Component, props, id } = this.props + return <Component {...props} id={id} actionsRef={(a) => (this.actions = a)} /> } componentDidMount() { - if(this.props.wrapper) { - this.props.wrapper.addEventListener('x-interaction.trigger-action', this); + if (this.props.wrapper) { + this.props.wrapper.addEventListener('x-interaction.trigger-action', this) } } componentWillUnmount() { - if(this.props.wrapper) { - this.props.wrapper.removeEventListener('x-interaction.trigger-action', this); + if (this.props.wrapper) { + this.props.wrapper.removeEventListener('x-interaction.trigger-action', this) } } handleEvent(event) { - const {action, args = []} = event.detail; + const { action, args = [] } = event.detail - if(this.actions && this.actions[action]) { - this.actions[action](...args); + if (this.actions && this.actions[action]) { + this.actions[action](...args) } } } export function hydrate() { if (typeof window === 'undefined') { - throw new Error('x-interaction hydrate should only be called in the browser'); + throw new Error('x-interaction hydrate should only be called in the browser') } if (!('_xDashInteractionHydrationData' in window)) { throw new Error( `x-interaction hydrate was called without hydration data available. this can happen if you call hydrate before the serialised data is available, or if you're not including the hydration data with your server-rendered markup.` - ); + ) } - const serialiserOrdering = `make sure you're always outputting the serialiser's data in the same request that the serialiser was created. see https://financial-times.github.io/x-dash/components/x-interaction/#hydrating for more details.`; + const serialiserOrdering = `make sure you're always outputting the serialiser's data in the same request that the serialiser was created. see https://financial-times.github.io/x-dash/components/x-interaction/#hydrating for more details.` window._xDashInteractionHydrationData.forEach(({ id, component, props }) => { - const wrapper = document.querySelector(`[data-x-dash-id="${id}"]`); + const wrapper = document.querySelector(`[data-x-dash-id="${id}"]`) - if(!wrapper) { + if (!wrapper) { throw new Error( `component markup for ${id} was not found on the page. It was expected to be an instance of ${component}. it's likely that this hydration data is from another request. ${serialiserOrdering}` - ); + ) } - const Component = getComponent(component); + const Component = getComponent(component) while (wrapper.firstChild) { - wrapper.removeChild(wrapper.firstChild); + wrapper.removeChild(wrapper.firstChild) } - render(<HydrationWrapper {...{ - Component, - props, - id, - wrapper, - }} />, wrapper); - }); - - document.querySelectorAll('[data-x-dash-id]').forEach(element => { - const {xDashId} = element.dataset; - - const hasData = window._xDashInteractionHydrationData.some( - ({ id }) => id === xDashId - ); - - if(!hasData) { + render( + <HydrationWrapper + {...{ + Component, + props, + id, + wrapper + }} + />, + wrapper + ) + }) + + document.querySelectorAll('[data-x-dash-id]').forEach((element) => { + const { xDashId } = element.dataset + + const hasData = window._xDashInteractionHydrationData.some(({ id }) => id === xDashId) + + if (!hasData) { throw new Error( `found component markup for ${xDashId} without any hydration data. it's likely that its hydration data has been output in another request, or that the component was rendered after the serialisation data was output. ${serialiserOrdering}` - ); + ) } - }); + }) } diff --git a/components/x-interaction/src/HydrationData.jsx b/components/x-interaction/src/HydrationData.jsx index b727b0120..d8129d29f 100644 --- a/components/x-interaction/src/HydrationData.jsx +++ b/components/x-interaction/src/HydrationData.jsx @@ -1,11 +1,17 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' -export const HydrationData = ({serialiser}) => { - if(serialiser) { - const data = serialiser.flushHydrationData(); +export const HydrationData = ({ serialiser }) => { + if (serialiser) { + const data = serialiser.flushHydrationData() - return <script dangerouslySetInnerHTML={{__html: `window._xDashInteractionHydrationData = ${JSON.stringify(data)}`}} />; + return ( + <script + dangerouslySetInnerHTML={{ + __html: `window._xDashInteractionHydrationData = ${JSON.stringify(data)}` + }} + /> + ) } - return null; -}; + return null +} diff --git a/components/x-interaction/src/Interaction.jsx b/components/x-interaction/src/Interaction.jsx index a75163cbf..033a5d430 100644 --- a/components/x-interaction/src/Interaction.jsx +++ b/components/x-interaction/src/Interaction.jsx @@ -1,70 +1,66 @@ -import { h } from '@financial-times/x-engine'; -import { InteractionClass } from './InteractionClass'; -import { InteractionSSR } from './InteractionSSR'; -import wrapComponentName from './concerns/wrap-component-name'; -import { registerComponent } from './concerns/register-component'; +import { h } from '@financial-times/x-engine' +import { InteractionClass } from './InteractionClass' +import { InteractionSSR } from './InteractionSSR' +import wrapComponentName from './concerns/wrap-component-name' +import { registerComponent } from './concerns/register-component' // use the class version for clientside and the static version for server -const Interaction = typeof window !== 'undefined' ? InteractionClass : InteractionSSR; +const Interaction = typeof window !== 'undefined' ? InteractionClass : InteractionSSR -const invoke = (fnOrObj, ...args) => typeof fnOrObj === 'function' - ? fnOrObj(...args) - : fnOrObj; +const invoke = (fnOrObj, ...args) => (typeof fnOrObj === 'function' ? fnOrObj(...args) : fnOrObj) export const withActions = (getActions, getDefaultState = {}) => (Component) => { - const _wraps = { getActions, getDefaultState, Component }; + const _wraps = { getActions, getDefaultState, Component } // if the component we're wrapping is already wrapped, we don't want // to wrap it further. so, discard its wrapper and rewrap the original // component with the new actions on top - if(Component._wraps) { - const wrappedGetActions = Component._wraps.getActions; - const wrappedGetDefaultState = Component._wraps.getDefaultState; + if (Component._wraps) { + const wrappedGetActions = Component._wraps.getActions + const wrappedGetDefaultState = Component._wraps.getDefaultState - Component = Component._wraps.Component; + Component = Component._wraps.Component - getActions = initialState => Object.assign( - invoke(wrappedGetActions, initialState), - invoke(_wraps.getActions, initialState) - ); + getActions = (initialState) => + Object.assign(invoke(wrappedGetActions, initialState), invoke(_wraps.getActions, initialState)) - getDefaultState = initialState => Object.assign( - invoke(wrappedGetDefaultState, initialState), - invoke(_wraps.getDefaultState, initialState) - ); + getDefaultState = (initialState) => + Object.assign( + invoke(wrappedGetDefaultState, initialState), + invoke(_wraps.getDefaultState, initialState) + ) } - function Enhanced({ - id, - actionsRef, - serialiser, - ...initialState - }) { - const actions = invoke(getActions, initialState); - const defaultState = invoke(getDefaultState, initialState); + function Enhanced({ id, actionsRef, serialiser, ...initialState }) { + const actions = invoke(getActions, initialState) + const defaultState = invoke(getDefaultState, initialState) - return <Interaction {...{ - id, - Component, - initialState: Object.assign({}, defaultState, initialState), - actionsRef, - serialiser, - actions, - }} />; + return ( + <Interaction + {...{ + id, + Component, + initialState: Object.assign({}, defaultState, initialState), + actionsRef, + serialiser, + actions + }} + /> + ) } // store what we're wrapping for later wrappers to replace - Enhanced._wraps = _wraps; + Enhanced._wraps = _wraps // set the displayName of the Enhanced component for debugging - wrapComponentName(Component, Enhanced); + wrapComponentName(Component, Enhanced) // register the component under its name for later hydration from serialised data - registerComponent(Enhanced); + registerComponent(Enhanced) - return Enhanced; -}; + return Enhanced +} -export { hydrate } from './Hydrate'; -export { HydrationData } from './HydrationData'; -export { Serialiser } from './concerns/serialiser'; +export { hydrate } from './Hydrate' +export { HydrationData } from './HydrationData' +export { Serialiser } from './concerns/serialiser' diff --git a/components/x-interaction/src/InteractionClass.jsx b/components/x-interaction/src/InteractionClass.jsx index bffa82a90..ea13b048b 100644 --- a/components/x-interaction/src/InteractionClass.jsx +++ b/components/x-interaction/src/InteractionClass.jsx @@ -1,17 +1,17 @@ -import { h, Component } from '@financial-times/x-engine'; -import { InteractionRender } from './InteractionRender'; -import mapValues from './concerns/map-values'; +import { h, Component } from '@financial-times/x-engine' +import { InteractionRender } from './InteractionRender' +import mapValues from './concerns/map-values' export class InteractionClass extends Component { constructor(props, ...args) { - super(props, ...args); + super(props, ...args) this.state = { state: {}, - inFlight: 0, - }; + inFlight: 0 + } - this.createActions(props); + this.createActions(props) } createActions(props) { @@ -20,47 +20,44 @@ export class InteractionClass extends Component { // setting loading back to false will happen in the same microtask and no // additional render will be scheduled. Promise.resolve().then(() => { - this.setState(({ inFlight }) => ({ inFlight: inFlight + 1 })); - }); + this.setState(({ inFlight }) => ({ inFlight: inFlight + 1 })) + }) - const stateUpdate = await Promise.resolve(func(...args)); + const stateUpdate = await Promise.resolve(func(...args)) - const nextState = typeof stateUpdate === 'function' - ? Object.assign( - this.state.state, - await Promise.resolve(stateUpdate(Object.assign( - {}, - props.initialState, - this.state.state - ))) - ) - : Object.assign(this.state.state, stateUpdate); + const nextState = + typeof stateUpdate === 'function' + ? Object.assign( + this.state.state, + await Promise.resolve(stateUpdate(Object.assign({}, props.initialState, this.state.state))) + ) + : Object.assign(this.state.state, stateUpdate) - return new Promise(resolve => - this.setState({state: nextState}, () => ( + return new Promise((resolve) => + this.setState({ state: nextState }, () => this.setState(({ inFlight }) => ({ inFlight: inFlight - 1 }), resolve) - )) - ); - }); + ) + ) + }) } componentWillReceiveProps(props) { - this.createActions(props); + this.createActions(props) } componentDidMount() { - if(this.props.actionsRef) { - this.props.actionsRef(this.actions); + if (this.props.actionsRef) { + this.props.actionsRef(this.actions) } } componentWillUnmount() { - if(this.props.actionsRef) { - this.props.actionsRef(null); + if (this.props.actionsRef) { + this.props.actionsRef(null) } } render() { - return <InteractionRender {...this.props} {...this.state} actions={this.actions} />; + return <InteractionRender {...this.props} {...this.state} actions={this.actions} /> } } diff --git a/components/x-interaction/src/InteractionRender.jsx b/components/x-interaction/src/InteractionRender.jsx index 1f33b27c8..ce12e805c 100644 --- a/components/x-interaction/src/InteractionRender.jsx +++ b/components/x-interaction/src/InteractionRender.jsx @@ -1,12 +1,5 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' -export const InteractionRender = ({ - id, - actions, - state, - initialState, - inFlight, - Component, -}) => ( +export const InteractionRender = ({ id, actions, state, initialState, inFlight, Component }) => ( <Component {...initialState} {...state} {...{ id, actions }} isLoading={inFlight > 0} /> -); +) diff --git a/components/x-interaction/src/InteractionSSR.jsx b/components/x-interaction/src/InteractionSSR.jsx index c5dc81eab..b7681140c 100644 --- a/components/x-interaction/src/InteractionSSR.jsx +++ b/components/x-interaction/src/InteractionSSR.jsx @@ -1,25 +1,27 @@ -import { h } from '@financial-times/x-engine'; -import getComponentName from './concerns/get-component-name'; -import shortId from '@quarterto/short-id'; +import { h } from '@financial-times/x-engine' +import getComponentName from './concerns/get-component-name' +import shortId from '@quarterto/short-id' -import {InteractionRender} from './InteractionRender'; +import { InteractionRender } from './InteractionRender' export const InteractionSSR = ({ initialState, Component, id = `${getComponentName(Component)}-${shortId()}`, actions, - serialiser, + serialiser }) => { - if(serialiser) { + if (serialiser) { serialiser.addData({ id, Component, props: initialState - }); + }) } - return <div data-x-dash-id={id}> - <InteractionRender {...{ Component, initialState, id, actions }} /> - </div>; -}; + return ( + <div data-x-dash-id={id}> + <InteractionRender {...{ Component, initialState, id, actions }} /> + </div> + ) +} diff --git a/components/x-interaction/src/concerns/get-component-name.js b/components/x-interaction/src/concerns/get-component-name.js index f72a088bf..bd14d40c3 100644 --- a/components/x-interaction/src/concerns/get-component-name.js +++ b/components/x-interaction/src/concerns/get-component-name.js @@ -1,6 +1,3 @@ -const getComponentName = Component => - Component.displayName - || Component.name - || 'Unknown'; +const getComponentName = (Component) => Component.displayName || Component.name || 'Unknown' -export default getComponentName; +export default getComponentName diff --git a/components/x-interaction/src/concerns/map-values.js b/components/x-interaction/src/concerns/map-values.js index d70398691..a7fe8e2d2 100644 --- a/components/x-interaction/src/concerns/map-values.js +++ b/components/x-interaction/src/concerns/map-values.js @@ -1,8 +1,10 @@ -const mapValues = (obj, fn) => Object.keys(obj).reduce( - (mapped, key) => Object.assign(mapped, { - [key]: fn(obj[key], key, obj), - }), - {} -); +const mapValues = (obj, fn) => + Object.keys(obj).reduce( + (mapped, key) => + Object.assign(mapped, { + [key]: fn(obj[key], key, obj) + }), + {} + ) -export default mapValues; +export default mapValues diff --git a/components/x-interaction/src/concerns/register-component.js b/components/x-interaction/src/concerns/register-component.js index 7b85f5d3e..f09653030 100644 --- a/components/x-interaction/src/concerns/register-component.js +++ b/components/x-interaction/src/concerns/register-component.js @@ -1,9 +1,9 @@ -const registeredComponents = {}; +const registeredComponents = {} export function registerComponent(component) { - registeredComponents[component.wrappedDisplayName] = component; + registeredComponents[component.wrappedDisplayName] = component } export function getComponent(name) { - return registeredComponents[name]; + return registeredComponents[name] } diff --git a/components/x-interaction/src/concerns/serialiser.js b/components/x-interaction/src/concerns/serialiser.js index 8297b2aec..6e252623a 100644 --- a/components/x-interaction/src/concerns/serialiser.js +++ b/components/x-interaction/src/concerns/serialiser.js @@ -1,35 +1,39 @@ -import { h, render } from '@financial-times/x-engine'; -import getComponentName from './get-component-name'; -import { HydrationData } from '../HydrationData'; +import { h, render } from '@financial-times/x-engine' +import getComponentName from './get-component-name' +import { HydrationData } from '../HydrationData' export class Serialiser { constructor() { - this.destroyed = false; - this.data = []; + this.destroyed = false + this.data = [] } - addData({id, Component, props}) { - if(this.destroyed) { - throw new Error(`an interaction component was rendered after flushHydrationData was called. ensure you're outputting the hydration data after rendering every component`); + addData({ id, Component, props }) { + if (this.destroyed) { + throw new Error( + `an interaction component was rendered after flushHydrationData was called. ensure you're outputting the hydration data after rendering every component` + ) } this.data.push({ id, component: getComponentName(Component), - props, - }); + props + }) } flushHydrationData() { - if(this.destroyed) { - throw new Error(`a Serialiser's flushHydrationData was called twice. ensure you're not reusing a Serialiser between requests`); + if (this.destroyed) { + throw new Error( + `a Serialiser's flushHydrationData was called twice. ensure you're not reusing a Serialiser between requests` + ) } - this.destroyed = true; - return this.data; + this.destroyed = true + return this.data } outputHydrationData() { - return render(h(HydrationData, {serialiser: this})); + return render(h(HydrationData, { serialiser: this })) } } diff --git a/components/x-interaction/src/concerns/wrap-component-name.js b/components/x-interaction/src/concerns/wrap-component-name.js index fa5a59359..db841f64f 100644 --- a/components/x-interaction/src/concerns/wrap-component-name.js +++ b/components/x-interaction/src/concerns/wrap-component-name.js @@ -1,9 +1,9 @@ -import getComponentName from './get-component-name'; +import getComponentName from './get-component-name' function wrapComponentName(Component, Enhanced) { - const originalDisplayName = getComponentName(Component); - Enhanced.displayName = `withActions(${originalDisplayName})`; - Enhanced.wrappedDisplayName = originalDisplayName; + const originalDisplayName = getComponentName(Component) + Enhanced.displayName = `withActions(${originalDisplayName})` + Enhanced.wrappedDisplayName = originalDisplayName } -export default wrapComponentName; +export default wrapComponentName diff --git a/components/x-live-blog-post/rollup.js b/components/x-live-blog-post/rollup.js index 0a1e8b89c..53b33f837 100644 --- a/components/x-live-blog-post/rollup.js +++ b/components/x-live-blog-post/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/LiveBlogPost.jsx', pkg }); +xRollup({ input: './src/LiveBlogPost.jsx', pkg }) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 23e943a09..c9d7c5b1c 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -1,7 +1,7 @@ -import { h } from '@financial-times/x-engine'; -import ShareButtons from './ShareButtons'; -import Timestamp from './Timestamp'; -import styles from './LiveBlogPost.scss'; +import { h } from '@financial-times/x-engine' +import ShareButtons from './ShareButtons' +import Timestamp from './Timestamp' +import styles from './LiveBlogPost.scss' const LiveBlogPost = (props) => { const { @@ -14,11 +14,12 @@ const LiveBlogPost = (props) => { publishedDate, isBreakingNews, articleUrl, - showShareButtons = false, - } = props; + showShareButtons = false + } = props return ( - <article className={`live-blog-post ${styles['live-blog-post']}`} + <article + className={`live-blog-post ${styles['live-blog-post']}`} data-trackable="live-post" id={`post-${id || postId}`} data-x-component="live-blog-post"> @@ -27,11 +28,13 @@ const LiveBlogPost = (props) => { </div> {isBreakingNews && <div className={styles['live-blog-post__breaking-news']}>Breaking news</div>} {title && <h1 className={styles['live-blog-post__title']}>{title}</h1>} - <div className={`${styles['live-blog-post__body']} n-content-body article--body`} dangerouslySetInnerHTML={{ __html: bodyHTML || content }} /> - {showShareButtons && - <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} + <div + className={`${styles['live-blog-post__body']} n-content-body article--body`} + dangerouslySetInnerHTML={{ __html: bodyHTML || content }} + /> + {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} </article> - ); -}; + ) +} -export { LiveBlogPost }; +export { LiveBlogPost } diff --git a/components/x-live-blog-post/src/ShareButtons.jsx b/components/x-live-blog-post/src/ShareButtons.jsx index 0c83dea48..0ed3ad888 100644 --- a/components/x-live-blog-post/src/ShareButtons.jsx +++ b/components/x-live-blog-post/src/ShareButtons.jsx @@ -1,15 +1,21 @@ -import { h } from '@financial-times/x-engine'; -import styles from './LiveBlogPost.scss'; +import { h } from '@financial-times/x-engine' +import styles from './LiveBlogPost.scss' export default ({ postId, articleUrl, title }) => { - const shareUrl = articleUrl ? new URL(articleUrl) : null; + const shareUrl = articleUrl ? new URL(articleUrl) : null if (shareUrl) { - shareUrl.hash = `post-${postId}`; + shareUrl.hash = `post-${postId}` } - const twitterUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(title)}&via=financialtimes`; - const facebookUrl = `http://www.facebook.com/sharer.php?u=${encodeURIComponent(shareUrl)}&t=${encodeURIComponent(title)}`; - const linkedInUrl = `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(title)}&source=Financial+Times`; + const twitterUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent( + shareUrl + )}&text=${encodeURIComponent(title)}&via=financialtimes` + const facebookUrl = `http://www.facebook.com/sharer.php?u=${encodeURIComponent( + shareUrl + )}&t=${encodeURIComponent(title)}` + const linkedInUrl = `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent( + shareUrl + )}&title=${encodeURIComponent(title)}&source=Financial+Times` return ( <div className={styles['live-blog-post__share-buttons']}> @@ -24,7 +30,9 @@ export default ({ postId, articleUrl, title }) => { rel="noopener" href={twitterUrl} data-trackable="twitter"> - <span className="o-share__text" aria-label={`Share ${title} on Twitter`}>Share on Twitter (opens new window)</span> + <span className="o-share__text" aria-label={`Share ${title} on Twitter`}> + Share on Twitter (opens new window) + </span> </a> </li> <li className="o-share__action" data-share="facebook"> @@ -33,7 +41,9 @@ export default ({ postId, articleUrl, title }) => { rel="noopener" href={facebookUrl} data-trackable="facebook"> - <span className="o-share__text" aria-label={`Share ${title} on Facebook`}>Share on Facebook (opens new window)</span> + <span className="o-share__text" aria-label={`Share ${title} on Facebook`}> + Share on Facebook (opens new window) + </span> </a> </li> <li className="o-share__action" data-share="linkedin"> @@ -42,11 +52,13 @@ export default ({ postId, articleUrl, title }) => { rel="noopener" href={linkedInUrl} data-trackable="linkedin"> - <span className="o-share__text" aria-label={`Share ${title} on LinkedIn`}>Share on LinkedIn (opens new window)</span> + <span className="o-share__text" aria-label={`Share ${title} on LinkedIn`}> + Share on LinkedIn (opens new window) + </span> </a> </li> </ul> </div> </div> - ); -}; + ) +} diff --git a/components/x-live-blog-post/src/Timestamp.jsx b/components/x-live-blog-post/src/Timestamp.jsx index 3948cdad9..7e6944ffb 100644 --- a/components/x-live-blog-post/src/Timestamp.jsx +++ b/components/x-live-blog-post/src/Timestamp.jsx @@ -1,25 +1,25 @@ -import { h } from '@financial-times/x-engine'; -import styles from './LiveBlogPost.scss'; +import { h } from '@financial-times/x-engine' +import styles from './LiveBlogPost.scss' export default ({ publishedTimestamp }) => { - const now = new Date(); - const oneDay = 24 * 60 * 60 * 1000; - const date = new Date(publishedTimestamp); - const formatted = date.toLocaleString(); + const now = new Date() + const oneDay = 24 * 60 * 60 * 1000 + const date = new Date(publishedTimestamp) + const formatted = date.toLocaleString() - let format; - let showExactTime; + let format + let showExactTime if (now.getTime() - date.getTime() < oneDay) { // display published date in 'xx minutes ago' format // and render exact time next to it - format = 'time-ago-no-seconds'; - showExactTime = true; + format = 'time-ago-no-seconds' + showExactTime = true } else { // don't display time string if the post is older than one day // because it is already included in the formatted timestamp - format = 'MMM dd, HH:mm'; - showExactTime = false; + format = 'MMM dd, HH:mm' + showExactTime = false } return ( @@ -30,16 +30,15 @@ export default ({ publishedTimestamp }) => { dateTime={publishedTimestamp} data-o-date-format={format} itemProp="datePublished"> - <span data-o-date-printer>{formatted}</span> - {showExactTime && ( - <span - className={`o-date ${styles['live-blog-post__timestamp-exact-time']}`} - data-o-date-printer - data-o-date-format="HH:mm" - itemProp="exactTime"> - </span> - )} + <span data-o-date-printer>{formatted}</span> + {showExactTime && ( + <span + className={`o-date ${styles['live-blog-post__timestamp-exact-time']}`} + data-o-date-printer + data-o-date-format="HH:mm" + itemProp="exactTime"></span> + )} </time> </div> - ); -}; + ) +} diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index 9b2c70e2a..256d35d1d 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -1,7 +1,7 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -import { LiveBlogPost } from '../LiveBlogPost'; +import { LiveBlogPost } from '../LiveBlogPost' const breakingNewsWordpress = { postId: '12345', @@ -11,7 +11,7 @@ const breakingNewsWordpress = { isBreakingNews: true, articleUrl: 'Https://www.ft.com', showShareButtons: true -}; +} const regularPostWordpress = { postId: '12345', @@ -31,7 +31,7 @@ const breakingNewsSpark = { isBreakingNews: true, articleUrl: 'Https://www.ft.com', showShareButtons: true -}; +} const regularPostSpark = { id: '12345', @@ -47,116 +47,116 @@ describe('x-live-blog-post', () => { describe('Spark cms', () => { describe('title property exists', () => { it('renders title', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) - expect(liveBlogPost.html()).toContain('Test title'); - expect(liveBlogPost.html()).toContain('</h1>'); - }); - }); + expect(liveBlogPost.html()).toContain('Test title') + expect(liveBlogPost.html()).toContain('</h1>') + }) + }) describe('title property is missing', () => { - let postWithoutTitle = Object.assign({}, regularPostSpark); + let postWithoutTitle = Object.assign({}, regularPostSpark) beforeAll(() => { delete postWithoutTitle.title - }); + }) it('skips rendering of the title', () => { - const liveBlogPost = mount(<LiveBlogPost {...postWithoutTitle} />); + const liveBlogPost = mount(<LiveBlogPost {...postWithoutTitle} />) - expect(liveBlogPost.html()).not.toContain('</h1>'); - }); - }); + expect(liveBlogPost.html()).not.toContain('</h1>') + }) + }) it('renders timestamp', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) - expect(liveBlogPost.html()).toContain(regularPostSpark.publishedTimestamp); - }); + expect(liveBlogPost.html()).toContain(regularPostSpark.publishedTimestamp) + }) it('renders sharing buttons', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) - expect(liveBlogPost.html()).toContain('o-share__icon--linkedin'); - }); + expect(liveBlogPost.html()).toContain('o-share__icon--linkedin') + }) it('renders breaking news tag when the post is a breaking news', () => { - const liveBlogPost = mount(<LiveBlogPost {...breakingNewsSpark} />); + const liveBlogPost = mount(<LiveBlogPost {...breakingNewsSpark} />) - expect(liveBlogPost.html()).toContain('Breaking news'); - }); + expect(liveBlogPost.html()).toContain('Breaking news') + }) it('does not render breaking news tag when the post is not breaking news', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) - expect(liveBlogPost.html()).not.toContain('Breaking news'); - }); + expect(liveBlogPost.html()).not.toContain('Breaking news') + }) it('does not escape content html', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) - expect(liveBlogPost.html()).toContain('<p><i>Test body</i></p>'); - }); - }); + expect(liveBlogPost.html()).toContain('<p><i>Test body</i></p>') + }) + }) describe('Wordpress cms', () => { describe('title property exists', () => { it('renders title', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />) - expect(liveBlogPost.html()).toContain('Test title'); - expect(liveBlogPost.html()).toContain('</h1>'); - }); - }); + expect(liveBlogPost.html()).toContain('Test title') + expect(liveBlogPost.html()).toContain('</h1>') + }) + }) describe('title property is missing', () => { - let postWithoutTitle = Object.assign({}, regularPostWordpress); + let postWithoutTitle = Object.assign({}, regularPostWordpress) beforeAll(() => { delete postWithoutTitle.title - }); + }) it('skips rendering of the title', () => { - const liveBlogPost = mount(<LiveBlogPost {...postWithoutTitle} />); + const liveBlogPost = mount(<LiveBlogPost {...postWithoutTitle} />) - expect(liveBlogPost.html()).not.toContain('</h1>'); - }); - }); + expect(liveBlogPost.html()).not.toContain('</h1>') + }) + }) it('renders timestamp', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />) - expect(liveBlogPost.html()).toContain(regularPostWordpress.publishedTimestamp); - }); + expect(liveBlogPost.html()).toContain(regularPostWordpress.publishedTimestamp) + }) it('renders sharing buttons', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />) - expect(liveBlogPost.html()).toContain('o-share__icon--linkedin'); - }); + expect(liveBlogPost.html()).toContain('o-share__icon--linkedin') + }) it('renders breaking news tag when the post is a breaking news', () => { - const liveBlogPost = mount(<LiveBlogPost {...breakingNewsWordpress} />); + const liveBlogPost = mount(<LiveBlogPost {...breakingNewsWordpress} />) - expect(liveBlogPost.html()).toContain('Breaking news'); - }); + expect(liveBlogPost.html()).toContain('Breaking news') + }) it('does not render breaking news tag when the post is not breaking news', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />) - expect(liveBlogPost.html()).not.toContain('Breaking news'); - }); + expect(liveBlogPost.html()).not.toContain('Breaking news') + }) it('does not escape content html', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />) - expect(liveBlogPost.html()).toContain('<p><i>Test body</i></p>'); - }); - }); + expect(liveBlogPost.html()).toContain('<p><i>Test body</i></p>') + }) + }) it('adds a data-x-component attribute', () => { - const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />); + const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) - expect(liveBlogPost.html()).toContain('data-x-component="live-blog-post"'); - }); -}); + expect(liveBlogPost.html()).toContain('data-x-component="live-blog-post"') + }) +}) diff --git a/components/x-live-blog-post/src/__tests__/ShareButtons.test.jsx b/components/x-live-blog-post/src/__tests__/ShareButtons.test.jsx index d85a030e1..eac1c8d5a 100644 --- a/components/x-live-blog-post/src/__tests__/ShareButtons.test.jsx +++ b/components/x-live-blog-post/src/__tests__/ShareButtons.test.jsx @@ -1,7 +1,7 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -import ShareButtons from '../ShareButtons'; +import ShareButtons from '../ShareButtons' const props = { postId: '12345', @@ -10,30 +10,32 @@ const props = { } describe('x-live-blog-post', () => { - describe('ShareButtons', () => { - it('renders correct twitter url', () => { - const shareButtons = mount(<ShareButtons {...props} />); - const twitterButton = shareButtons.find('.o-share__icon--twitter').first(); + const shareButtons = mount(<ShareButtons {...props} />) + const twitterButton = shareButtons.find('.o-share__icon--twitter').first() - expect(twitterButton.prop('href')).toEqual('https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2F%23post-12345&text=Test%20title&via=financialtimes'); - }); + expect(twitterButton.prop('href')).toEqual( + 'https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2F%23post-12345&text=Test%20title&via=financialtimes' + ) + }) it('renders correct facebook url', () => { - const shareButtons = mount(<ShareButtons {...props} />); - const facebookButton = shareButtons.find('.o-share__icon--facebook').first(); + const shareButtons = mount(<ShareButtons {...props} />) + const facebookButton = shareButtons.find('.o-share__icon--facebook').first() - expect(facebookButton.prop('href')).toEqual('http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2F%23post-12345&t=Test%20title'); - }); + expect(facebookButton.prop('href')).toEqual( + 'http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2F%23post-12345&t=Test%20title' + ) + }) it('renders correct linkedin url', () => { - const shareButtons = mount(<ShareButtons {...props} />); - const linkedinButton = shareButtons.find('.o-share__icon--linkedin').first(); - - expect(linkedinButton.prop('href')).toEqual('http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2F%23post-12345&title=Test%20title&source=Financial+Times'); - }); + const shareButtons = mount(<ShareButtons {...props} />) + const linkedinButton = shareButtons.find('.o-share__icon--linkedin').first() + expect(linkedinButton.prop('href')).toEqual( + 'http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2F%23post-12345&title=Test%20title&source=Financial+Times' + ) + }) }) - -}); +}) diff --git a/components/x-live-blog-post/src/__tests__/Timestamp.test.jsx b/components/x-live-blog-post/src/__tests__/Timestamp.test.jsx index 26c686cf2..7443ff891 100644 --- a/components/x-live-blog-post/src/__tests__/Timestamp.test.jsx +++ b/components/x-live-blog-post/src/__tests__/Timestamp.test.jsx @@ -1,61 +1,58 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -import Timestamp from '../Timestamp'; +import Timestamp from '../Timestamp' function twoDaysAgo() { - const now = new Date(); - const twoDays = 2 * 24 * 60 * 60 * 1000; - return new Date(now.getTime() - twoDays); + const now = new Date() + const twoDays = 2 * 24 * 60 * 60 * 1000 + return new Date(now.getTime() - twoDays) } function twoMinutesAgo() { - const now = new Date(); - const twoMinutes = 2 * 60 * 1000; - return new Date(now.getTime() - twoMinutes); + const now = new Date() + const twoMinutes = 2 * 60 * 1000 + return new Date(now.getTime() - twoMinutes) } describe('x-live-blog-post', () => { - describe('TimeStamp', () => { - describe('when timestamp is older than 24 hours', () => { it('renders the time in MMM dd, HH:mm', () => { - const date = twoDaysAgo(); - const timestamp = mount(<Timestamp publishedTimestamp={date.toISOString()} />); - const dateEl = timestamp.find('time[data-o-component="o-date"]'); + const date = twoDaysAgo() + const timestamp = mount(<Timestamp publishedTimestamp={date.toISOString()} />) + const dateEl = timestamp.find('time[data-o-component="o-date"]') - expect(dateEl.prop('data-o-date-format')).toEqual('MMM dd, HH:mm'); - expect(dateEl.prop('dateTime')).toEqual(date.toISOString()); - expect(dateEl.text()).toEqual(date.toLocaleString()); - }); + expect(dateEl.prop('data-o-date-format')).toEqual('MMM dd, HH:mm') + expect(dateEl.prop('dateTime')).toEqual(date.toISOString()) + expect(dateEl.text()).toEqual(date.toLocaleString()) + }) it('does not render exact time', () => { - const timestamp = mount(<Timestamp publishedTimestamp={twoDaysAgo().toISOString()} />); - const exactTimeEl = timestamp.find('span').at(1); + const timestamp = mount(<Timestamp publishedTimestamp={twoDaysAgo().toISOString()} />) + const exactTimeEl = timestamp.find('span').at(1) - expect(exactTimeEl).not.toExist(); - }); - }); + expect(exactTimeEl).not.toExist() + }) + }) describe('when timestamp is in the last 24 hours', () => { it('renders the time in "time ago" format', () => { - const date = twoMinutesAgo(); - const timestamp = mount(<Timestamp publishedTimestamp={date.toISOString()} />); - const dateEl = timestamp.find('time[data-o-component="o-date"]'); + const date = twoMinutesAgo() + const timestamp = mount(<Timestamp publishedTimestamp={date.toISOString()} />) + const dateEl = timestamp.find('time[data-o-component="o-date"]') - expect(dateEl.prop('data-o-date-format')).toEqual('time-ago-no-seconds'); - expect(dateEl.prop('dateTime')).toEqual(date.toISOString()); - expect(dateEl.text()).toEqual(date.toLocaleString()); - }); + expect(dateEl.prop('data-o-date-format')).toEqual('time-ago-no-seconds') + expect(dateEl.prop('dateTime')).toEqual(date.toISOString()) + expect(dateEl.text()).toEqual(date.toLocaleString()) + }) it('renders the exact time', () => { - const timestamp = mount(<Timestamp publishedTimestamp={twoMinutesAgo().toISOString()} />); - const exactTimeEl = timestamp.find('span').at(1); - - expect(exactTimeEl.prop('data-o-date-format')).toEqual('HH:mm'); - }); - }); - - }); -}); + const timestamp = mount(<Timestamp publishedTimestamp={twoMinutesAgo().toISOString()} />) + const exactTimeEl = timestamp.find('span').at(1) + + expect(exactTimeEl.prop('data-o-date-format')).toEqual('HH:mm') + }) + }) + }) +}) diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index e6a3a5fef..cced81f06 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -1,25 +1,26 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { withKnobs, text, boolean } from '@storybook/addon-knobs'; -import { LiveBlogPost } from '../src/LiveBlogPost'; +import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs, text, boolean } from '@storybook/addon-knobs' +import { LiveBlogPost } from '../src/LiveBlogPost' const defaultProps = { id: 12345, title: 'Turkey’s virus deaths may be 25% higher than official figure', - bodyHTML: '<p>Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.</p>\n<p>Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.</p>\n<p>But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.</p>\n<p><a href="https://www.ft.com/content/80bb222c-b6eb-40ea-8014-563cbe9e0117" target="_blank">Read the article here</a></p>\n<p><img class="picture" src="http://blogs.ft.com/the-world/files/2020/05/istanbul_excess_morts_l.jpg"></p>', + bodyHTML: + '<p>Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.</p>\n<p>Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.</p>\n<p>But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.</p>\n<p><a href="https://www.ft.com/content/80bb222c-b6eb-40ea-8014-563cbe9e0117" target="_blank">Read the article here</a></p>\n<p><img class="picture" src="http://blogs.ft.com/the-world/files/2020/05/istanbul_excess_morts_l.jpg"></p>', isBreakingNews: false, publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', - showShareButtons: true, -}; + showShareButtons: true +} -const toggleTitle = () => text('Title', defaultProps.title); -const toggleShowBreakingNews = () => boolean('Show breaking news', defaultProps.isBreakingNews); -const toggleContent = () => text('Body HTML', defaultProps.bodyHTML); -const togglePostId = () => text('ID', defaultProps.id); -const togglePublishedTimestamp = () => text('Published date', defaultProps.publishedDate); -const toggleArticleUrl = () => text('Article URL', defaultProps.articleUrl); -const toggleShowShareButtons = () => boolean('Show share buttons', defaultProps.showShareButtons); +const toggleTitle = () => text('Title', defaultProps.title) +const toggleShowBreakingNews = () => boolean('Show breaking news', defaultProps.isBreakingNews) +const toggleContent = () => text('Body HTML', defaultProps.bodyHTML) +const togglePostId = () => text('ID', defaultProps.id) +const togglePublishedTimestamp = () => text('Published date', defaultProps.publishedDate) +const toggleArticleUrl = () => text('Article URL', defaultProps.articleUrl) +const toggleShowShareButtons = () => boolean('Show share buttons', defaultProps.showShareButtons) storiesOf('x-live-blog-post', module) .addDecorator(withKnobs) @@ -36,8 +37,8 @@ storiesOf('x-live-blog-post', module) id: togglePostId(), publishedDate: togglePublishedTimestamp(), articleUrl: toggleArticleUrl(), - showShareButtons: toggleShowShareButtons(), - }; + showShareButtons: toggleShowShareButtons() + } - return <LiveBlogPost {...defaultProps} {...knobs} />; - }); + return <LiveBlogPost {...defaultProps} {...knobs} /> + }) diff --git a/components/x-live-blog-wrapper/rollup.js b/components/x-live-blog-wrapper/rollup.js index 1a89a8955..d32376e11 100644 --- a/components/x-live-blog-wrapper/rollup.js +++ b/components/x-live-blog-wrapper/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/LiveBlogWrapper.jsx', pkg }); +xRollup({ input: './src/LiveBlogWrapper.jsx', pkg }) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 093ea214b..cc89bc24c 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -1,71 +1,73 @@ -import { h } from '@financial-times/x-engine'; -import { LiveBlogPost } from '@financial-times/x-live-blog-post'; -import { withActions } from '@financial-times/x-interaction'; -import { listenToLiveBlogEvents } from './LiveEventListener'; +import { h } from '@financial-times/x-engine' +import { LiveBlogPost } from '@financial-times/x-live-blog-post' +import { withActions } from '@financial-times/x-interaction' +import { listenToLiveBlogEvents } from './LiveEventListener' const withLiveBlogWrapperActions = withActions({ - insertPost (post) { + insertPost(post) { return (props) => { - props.posts.unshift(post); + props.posts.unshift(post) - return props; - }; + return props + } }, - updatePost (updated) { + updatePost(updated) { return (props) => { - const index = props.posts.findIndex(post => post.id === updated.id); + const index = props.posts.findIndex((post) => post.id === updated.id) if (index >= 0) { - props.posts[index] = updated; + props.posts[index] = updated } - return props; - }; + return props + } }, - deletePost (postId) { + deletePost(postId) { return (props) => { - const index = props.posts.findIndex(post => post.id === postId); + const index = props.posts.findIndex((post) => post.id === postId) if (index >= 0) { - props.posts.splice(index, 1); + props.posts.splice(index, 1) } - return props; - }; + return props + } } -}); +}) const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id, liveBlogWrapperElementRef }) => { posts.sort((a, b) => { - const timestampA = a.publishedDate || a.publishedTimestamp; - const timestampB = b.publishedDate || b.publishedTimestamp; + const timestampA = a.publishedDate || a.publishedTimestamp + const timestampB = b.publishedDate || b.publishedTimestamp // Newer posts on top if (timestampA > timestampB) { - return -1; + return -1 } if (timestampB > timestampA) { - return 1; + return 1 } - return 0; - }); + return 0 + }) - const postElements = posts.map(post => - <LiveBlogPost key={`live-blog-post-${post.id}`} + const postElements = posts.map((post) => ( + <LiveBlogPost + key={`live-blog-post-${post.id}`} {...post} articleUrl={articleUrl} - showShareButtons={showShareButtons}/> - ); + showShareButtons={showShareButtons} + /> + )) return ( - <div className='x-live-blog-wrapper' data-live-blog-wrapper-id={id} ref={liveBlogWrapperElementRef}> + <div className="x-live-blog-wrapper" data-live-blog-wrapper-id={id} ref={liveBlogWrapperElementRef}> {postElements} </div> - ); + ) } -const LiveBlogWrapper = withLiveBlogWrapperActions(BaseLiveBlogWrapper); +const LiveBlogWrapper = withLiveBlogWrapperActions(BaseLiveBlogWrapper) -export { LiveBlogWrapper, listenToLiveBlogEvents }; +export { LiveBlogWrapper, listenToLiveBlogEvents } diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index e5d84b8b8..5b5bf8e4a 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -1,16 +1,15 @@ const parsePost = (event) => { - const post = JSON.parse(event.data); + const post = JSON.parse(event.data) if (!post || !post.id) { - return; + return } - return post; -}; - + return post +} const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, actions }) => { - const wrapper = document.querySelector(`[data-live-blog-wrapper-id="${liveBlogWrapperElementId}"]`); + const wrapper = document.querySelector(`[data-live-blog-wrapper-id="${liveBlogWrapperElementId}"]`) const invokeAction = (action, args) => { if (actions) { @@ -23,7 +22,7 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, // // For more information: // https://github.com/Financial-Times/x-dash/tree/master/components/x-interaction#triggering-actions-externally - actions[action](...args); + actions[action](...args) } else { // When the component is rendered at the server side, we don't have a reference to // the actions object. HydrationWrapper in x-interaction listens to this specific @@ -32,14 +31,9 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, // If no 'actions' argument is passed when calling listenToLiveBlogEvents // function, we assume the component is rendered at the server side and trigger // the actions using this method. - wrapper.dispatchEvent( - new CustomEvent( - 'x-interaction.trigger-action', - { detail: { action, args } } - ) - ); + wrapper.dispatchEvent(new CustomEvent('x-interaction.trigger-action', { detail: { action, args } })) } - }; + } const dispatchLiveUpdateEvent = (eventType, data) => { /* @@ -68,46 +62,45 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, > This is because even though setTimeout was called with a delay of zero, it's placed on > a queue and scheduled to run at the next opportunity; not immediately. */ - window.setTimeout( - () => wrapper.dispatchEvent(new CustomEvent(eventType, { detail: data })), - 0); - }; + window.setTimeout(() => wrapper.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0) + } - const eventSource = new EventSource(`https://next-live-event.ft.com/v2/liveblog/${liveBlogPackageUuid}`, { withCredentials: true }); + const eventSource = new EventSource(`https://next-live-event.ft.com/v2/liveblog/${liveBlogPackageUuid}`, { + withCredentials: true + }) eventSource.addEventListener('insert-post', (event) => { - const post = parsePost(event); + const post = parsePost(event) if (!post) { - return; + return } - invokeAction('insertPost', [ post ]); - dispatchLiveUpdateEvent('LiveBlogWrapper.INSERT_POST', { post }); - }); + invokeAction('insertPost', [post]) + dispatchLiveUpdateEvent('LiveBlogWrapper.INSERT_POST', { post }) + }) eventSource.addEventListener('update-post', (event) => { - const post = parsePost(event); + const post = parsePost(event) if (!post) { - return; + return } - invokeAction('updatePost', [ post ]); - dispatchLiveUpdateEvent('LiveBlogWrapper.UPDATE_POST', { post }); - }); + invokeAction('updatePost', [post]) + dispatchLiveUpdateEvent('LiveBlogWrapper.UPDATE_POST', { post }) + }) eventSource.addEventListener('delete-post', (event) => { - const post = parsePost(event); + const post = parsePost(event) if (!post) { - return; + return } - invokeAction('deletePost', [ post.id ]) - dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.id }); - }); - -}; + invokeAction('deletePost', [post.id]) + dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.id }) + }) +} -export { listenToLiveBlogEvents }; +export { listenToLiveBlogEvents } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index f5f28a770..2f4be81f2 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -1,7 +1,7 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -import { LiveBlogWrapper } from '../LiveBlogWrapper'; +import { LiveBlogWrapper } from '../LiveBlogWrapper' const post1 = { id: '1', @@ -11,7 +11,7 @@ const post1 = { isBreakingNews: true, articleUrl: 'https://www.ft.com', showShareButtons: true -}; +} const post2 = { id: '2', @@ -25,71 +25,71 @@ const post2 = { describe('x-live-blog-wrapper', () => { it('renders initial posts', () => { - const posts = [ post1, post2 ]; - const liveBlogWrapper = mount(<LiveBlogWrapper posts={posts} />); + const posts = [post1, post2] + const liveBlogWrapper = mount(<LiveBlogWrapper posts={posts} />) - expect(liveBlogWrapper.html()).toContain('Post 1 Title'); - expect(liveBlogWrapper.html()).toContain('Post 1 body'); - expect(liveBlogWrapper.html()).toContain('Post 2 Title'); - expect(liveBlogWrapper.html()).toContain('Post 2 body'); - }); + expect(liveBlogWrapper.html()).toContain('Post 1 Title') + expect(liveBlogWrapper.html()).toContain('Post 1 body') + expect(liveBlogWrapper.html()).toContain('Post 2 Title') + expect(liveBlogWrapper.html()).toContain('Post 2 body') + }) it('orders posts by date - new posts on top', () => { - const posts = [ post1, post2 ]; - const liveBlogWrapper = mount(<LiveBlogWrapper posts={posts} />); + const posts = [post1, post2] + const liveBlogWrapper = mount(<LiveBlogWrapper posts={posts} />) - const articles = liveBlogWrapper.find('article'); - expect(articles.at(0).html()).toContain('Post 2 Title'); - expect(articles.at(1).html()).toContain('Post 1 Title'); - }); -}); + const articles = liveBlogWrapper.find('article') + expect(articles.at(0).html()).toContain('Post 2 Title') + expect(articles.at(1).html()).toContain('Post 1 Title') + }) +}) describe('liveBlogWrapperActions', () => { - let posts; - let actions; + let posts + let actions beforeEach(() => { - posts = [ post1, post2 ]; + posts = [post1, post2] // liveBlogActions are not exported from the module, but we can access them via // the props of LiveBlogWrapper component. - const liveBlogWrapper = LiveBlogWrapper({}); - actions = liveBlogWrapper.props.actions; - }); + const liveBlogWrapper = LiveBlogWrapper({}) + actions = liveBlogWrapper.props.actions + }) it('inserts a new post to the top of the list', () => { const post3 = { id: '3' - }; + } // insertPost function returns another function that takes the list of component props // as an argument and returns the updated props. - actions.insertPost(post3)({ posts }); + actions.insertPost(post3)({ posts }) - expect(posts.length).toEqual(3); - expect(posts[0].id).toEqual('3'); - }); + expect(posts.length).toEqual(3) + expect(posts[0].id).toEqual('3') + }) it('updates a post', () => { const updatedPost2 = { id: '2', title: 'Updated title' - }; + } // updatePost function returns another function that takes the list of component props // as an argument and returns the updated props. - actions.updatePost(updatedPost2)({ posts }); + actions.updatePost(updatedPost2)({ posts }) - expect(posts.length).toEqual(2); - expect(posts[1].title).toEqual('Updated title'); - }); + expect(posts.length).toEqual(2) + expect(posts[1].title).toEqual('Updated title') + }) it('deletes a post', () => { // deletePost function returns another function that takes the list of component props // as an argument and returns the updated props. - actions.deletePost('1')({ posts }); + actions.deletePost('1')({ posts }) - expect(posts.length).toEqual(1); - expect(posts[0].id).toEqual('2'); - }); -}); + expect(posts.length).toEqual(1) + expect(posts[0].id).toEqual('2') + }) +}) diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 9b639afd5..148b938c7 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -1,8 +1,8 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { withKnobs, text } from '@storybook/addon-knobs'; -import { LiveBlogWrapper } from '../src/LiveBlogWrapper'; -import '../../x-live-blog-post/dist/LiveBlogPost.css'; +import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs, text } from '@storybook/addon-knobs' +import { LiveBlogWrapper } from '../src/LiveBlogWrapper' +import '../../x-live-blog-post/dist/LiveBlogPost.css' const defaultProps = { message: 'Test', @@ -14,7 +14,7 @@ const defaultProps = { isBreakingNews: false, publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', - showShareButtons: true, + showShareButtons: true }, { id: 12346, @@ -23,7 +23,7 @@ const defaultProps = { isBreakingNews: true, publishedDate: '2020-05-13T19:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', - showShareButtons: true, + showShareButtons: true }, { id: 12347, @@ -32,12 +32,12 @@ const defaultProps = { isBreakingNews: false, publishedDate: '2020-05-13T20:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', - showShareButtons: true, + showShareButtons: true } ] -}; +} -const toggleMessage = () => text('Message', defaultProps.message); +const toggleMessage = () => text('Message', defaultProps.message) storiesOf('x-live-blog-wrapper', module) .addDecorator(withKnobs) @@ -48,8 +48,8 @@ storiesOf('x-live-blog-wrapper', module) }) .add('Content Body', () => { const knobs = { - message: toggleMessage(), - }; + message: toggleMessage() + } - return <LiveBlogWrapper {...defaultProps} {...knobs} />; - }); + return <LiveBlogWrapper {...defaultProps} {...knobs} /> + }) diff --git a/components/x-podcast-launchers/rollup.js b/components/x-podcast-launchers/rollup.js index 167dadc15..2242719a3 100644 --- a/components/x-podcast-launchers/rollup.js +++ b/components/x-podcast-launchers/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/PodcastLaunchers.jsx', pkg }); +xRollup({ input: './src/PodcastLaunchers.jsx', pkg }) diff --git a/components/x-podcast-launchers/src/PodcastLaunchers.jsx b/components/x-podcast-launchers/src/PodcastLaunchers.jsx index 21a70c097..0021d0306 100644 --- a/components/x-podcast-launchers/src/PodcastLaunchers.jsx +++ b/components/x-podcast-launchers/src/PodcastLaunchers.jsx @@ -1,94 +1,91 @@ -import { h, Component } from '@financial-times/x-engine'; -import { FollowButton } from '@financial-times/x-follow-button'; -import generateAppLinks from './generate-app-links'; -import generateRSSUrl from './generate-rss-url'; -import acastSeriesIds from './config/series-ids'; -import styles from './PodcastLaunchers.scss'; -import copyToClipboard from './copy-to-clipboard'; +import { h, Component } from '@financial-times/x-engine' +import { FollowButton } from '@financial-times/x-follow-button' +import generateAppLinks from './generate-app-links' +import generateRSSUrl from './generate-rss-url' +import acastSeriesIds from './config/series-ids' +import styles from './PodcastLaunchers.scss' +import copyToClipboard from './copy-to-clipboard' const rssUrlWrapperInner = [ - styles["o-forms-input--suffix"], - styles["o-forms-input--text"], - styles["o-forms-input"] -].join(' '); + styles['o-forms-input--suffix'], + styles['o-forms-input--text'], + styles['o-forms-input'] +].join(' ') -const noAppWrapperStyles = [ - 'podcast-launchers__no-app-wrapper', - styles.noAppWrapper -].join(' '); +const noAppWrapperStyles = ['podcast-launchers__no-app-wrapper', styles.noAppWrapper].join(' ') -function defaultFollowButtonRender (conceptId, conceptName, csrfToken, isFollowed) { +function defaultFollowButtonRender(conceptId, conceptName, csrfToken, isFollowed) { return ( <FollowButton conceptId={conceptId} conceptName={conceptName} csrfToken={csrfToken} isFollowed={isFollowed} - />); + /> + ) } class PodcastLaunchers extends Component { constructor(props) { - super(props); + super(props) this.state = { rssUrl: '' } } componentDidMount() { - const { conceptId, acastRSSHost, acastAccessToken } = this.props; - const acastSeries = acastSeriesIds.get(conceptId); + const { conceptId, acastRSSHost, acastAccessToken } = this.props + const acastSeries = acastSeriesIds.get(conceptId) if (acastSeries) { this.setState({ rssUrl: generateRSSUrl(acastRSSHost, acastSeries, acastAccessToken) - }); + }) } - } render() { - const { rssUrl } = this.state; + const { rssUrl } = this.state const { conceptId, conceptName, csrfToken, isFollowed, renderFollowButton } = this.props - const followButton = typeof renderFollowButton === 'function' ? renderFollowButton : defaultFollowButtonRender; + const followButton = + typeof renderFollowButton === 'function' ? renderFollowButton : defaultFollowButtonRender - return rssUrl && ( - <div className={styles.container} data-trackable='podcast-launchers'> - <h2 className={styles.headingChooseApp}>Subscribe via your installed podcast app</h2> - <ul className={styles.podcastAppLinksWrapper}> - {generateAppLinks(rssUrl).map(({ name, url, trackingId }) => ( - <li key={name}> - <a - href={url} - className={styles.podcastAppLink} - data-trackable={trackingId}> - {name} - </a> - </li> - ))} + return ( + rssUrl && ( + <div className={styles.container} data-trackable="podcast-launchers"> + <h2 className={styles.headingChooseApp}>Subscribe via your installed podcast app</h2> + <ul className={styles.podcastAppLinksWrapper}> + {generateAppLinks(rssUrl).map(({ name, url, trackingId }) => ( + <li key={name}> + <a href={url} className={styles.podcastAppLink} data-trackable={trackingId}> + {name} + </a> + </li> + ))} - <li key='Rss Url' className={styles.rssUrlWrapper}> - <span className={rssUrlWrapperInner}> - <input value={rssUrl} type='text' readOnly/> + <li key="Rss Url" className={styles.rssUrlWrapper}> + <span className={rssUrlWrapperInner}> + <input value={rssUrl} type="text" readOnly /> <button className={styles.rssUrlCopyButton} onClick={copyToClipboard} data-url={rssUrl} - data-trackable='copy-rss' - type='button'> + data-trackable="copy-rss" + type="button"> Copy RSS </button> - </span> - </li> - </ul> + </span> + </li> + </ul> - <div className={noAppWrapperStyles}> - <h2 className={styles.headingNoApp}>Can’t see your podcast app?</h2> - <p className={styles.textNoApp}>Get updates for new episodes</p> - {followButton(conceptId, conceptName, csrfToken, isFollowed)} + <div className={noAppWrapperStyles}> + <h2 className={styles.headingNoApp}>Can’t see your podcast app?</h2> + <p className={styles.textNoApp}>Get updates for new episodes</p> + {followButton(conceptId, conceptName, csrfToken, isFollowed)} + </div> </div> - </div> + ) ) } } -export { PodcastLaunchers }; +export { PodcastLaunchers } diff --git a/components/x-podcast-launchers/src/__tests__/PodcastLaunchers.test.jsx b/components/x-podcast-launchers/src/__tests__/PodcastLaunchers.test.jsx index e7f9e695a..d1fd211e1 100644 --- a/components/x-podcast-launchers/src/__tests__/PodcastLaunchers.test.jsx +++ b/components/x-podcast-launchers/src/__tests__/PodcastLaunchers.test.jsx @@ -1,23 +1,28 @@ -import { h } from '@financial-times/x-engine'; -import { brand } from '@financial-times/n-concept-ids'; -import renderer from 'react-test-renderer'; -jest.mock('../PodcastLaunchers.scss', () => ({})); -import { PodcastLaunchers } from '../PodcastLaunchers'; +import { h } from '@financial-times/x-engine' +import { brand } from '@financial-times/n-concept-ids' +import renderer from 'react-test-renderer' +jest.mock('../PodcastLaunchers.scss', () => ({})) +import { PodcastLaunchers } from '../PodcastLaunchers' - -const acastRSSHost = 'https://acast.access'; -const acastAccessToken = '123-abc'; +const acastRSSHost = 'https://acast.access' +const acastAccessToken = '123-abc' describe('PodcastLaunchers', () => { it('should hide itself if an RSS URL could not be generated', () => { expect( - renderer.create(<PodcastLaunchers conceptId='123-abc' {...{acastRSSHost, acastAccessToken}} />).toJSON() - ).toMatchSnapshot(); - }); + renderer + .create(<PodcastLaunchers conceptId="123-abc" {...{ acastRSSHost, acastAccessToken }} />) + .toJSON() + ).toMatchSnapshot() + }) it('should render the app links based on concept Id', () => { expect( - renderer.create(<PodcastLaunchers conceptId={brand.rachmanReviewPodcast} {...{acastRSSHost, acastAccessToken}} />).toJSON() - ).toMatchSnapshot(); - }); + renderer + .create( + <PodcastLaunchers conceptId={brand.rachmanReviewPodcast} {...{ acastRSSHost, acastAccessToken }} /> + ) + .toJSON() + ).toMatchSnapshot() + }) }) diff --git a/components/x-podcast-launchers/src/__tests__/generate-app-links.test.js b/components/x-podcast-launchers/src/__tests__/generate-app-links.test.js index 1c81177b7..b8607a973 100644 --- a/components/x-podcast-launchers/src/__tests__/generate-app-links.test.js +++ b/components/x-podcast-launchers/src/__tests__/generate-app-links.test.js @@ -1,11 +1,8 @@ - -import generateAppLinks from '../generate-app-links'; +import generateAppLinks from '../generate-app-links' describe('generate-app-links', () => { it('should generate each app link with the urlencoded RSS url', () => { - const rssUrl = 'https://acast.access/rss/ft-news/&£@1234'; - expect( - generateAppLinks(rssUrl) - ).toMatchSnapshot(); + const rssUrl = 'https://acast.access/rss/ft-news/&£@1234' + expect(generateAppLinks(rssUrl)).toMatchSnapshot() }) }) diff --git a/components/x-podcast-launchers/src/__tests__/generate-rss-url.test.js b/components/x-podcast-launchers/src/__tests__/generate-rss-url.test.js index 7eb6de461..00fffa151 100644 --- a/components/x-podcast-launchers/src/__tests__/generate-rss-url.test.js +++ b/components/x-podcast-launchers/src/__tests__/generate-rss-url.test.js @@ -1,21 +1,20 @@ +import generateRSSUrl from '../generate-rss-url' -import generateRSSUrl from '../generate-rss-url'; - -jest.mock('nanoid'); -import nanoid from 'nanoid'; +jest.mock('nanoid') +import nanoid from 'nanoid' describe('generate-app-links', () => { it('returns the acast access url from showId and token', () => { - expect( - generateRSSUrl('https://access.acast.cloud', 'ft-news-extra', '123-456') - ).toEqual('https://access.acast.cloud/rss/ft-news-extra/123-456') - }); + expect(generateRSSUrl('https://access.acast.cloud', 'ft-news-extra', '123-456')).toEqual( + 'https://access.acast.cloud/rss/ft-news-extra/123-456' + ) + }) it('generates a random token if one is not provided', () => { - nanoid.mockImplementation(() => 'abc-123'); + nanoid.mockImplementation(() => 'abc-123') - expect( - generateRSSUrl('https://access.acast.cloud', 'ft-news-extra') - ).toMatch('https://access.acast.cloud/rss/ft-news-extra/abc-123') - }); -}); + expect(generateRSSUrl('https://access.acast.cloud', 'ft-news-extra')).toMatch( + 'https://access.acast.cloud/rss/ft-news-extra/abc-123' + ) + }) +}) diff --git a/components/x-podcast-launchers/src/config/app-links.js b/components/x-podcast-launchers/src/config/app-links.js index 4e2a20c2d..aa0986eaa 100644 --- a/components/x-podcast-launchers/src/config/app-links.js +++ b/components/x-podcast-launchers/src/config/app-links.js @@ -1,33 +1,32 @@ -export default -[ +export default [ { - "name": "Apple Podcasts", - "template" : "podcast://{url}", - "includeProtocol": false, - "trackingId": "apple-podcasts", + name: 'Apple Podcasts', + template: 'podcast://{url}', + includeProtocol: false, + trackingId: 'apple-podcasts' }, { - "name": "Overcast", - "template": "overcast://x-callback-url/add?url={url}", - "includeProtocol": true, - "trackingId": "overcast", + name: 'Overcast', + template: 'overcast://x-callback-url/add?url={url}', + includeProtocol: true, + trackingId: 'overcast' }, { - "name": "Pocket Casts", - "template": "pktc://subscribe/{url}", - "includeProtocol": false, - "trackingId": "pocket-casts", + name: 'Pocket Casts', + template: 'pktc://subscribe/{url}', + includeProtocol: false, + trackingId: 'pocket-casts' }, { - "name": "Podcast Addict", - "template": "podcastaddict://{url}", - "includeProtocol": false, - "trackingId": "podcast-addict", + name: 'Podcast Addict', + template: 'podcastaddict://{url}', + includeProtocol: false, + trackingId: 'podcast-addict' }, { - "name": "Acast", - "template" : "acast://subscribe/{url}", - "includeProtocol": true, - "trackingId": "acast", + name: 'Acast', + template: 'acast://subscribe/{url}', + includeProtocol: true, + trackingId: 'acast' } ] diff --git a/components/x-podcast-launchers/src/config/series-ids.js b/components/x-podcast-launchers/src/config/series-ids.js index 110edabc2..9af5e0bb0 100644 --- a/components/x-podcast-launchers/src/config/series-ids.js +++ b/components/x-podcast-launchers/src/config/series-ids.js @@ -1,5 +1,3 @@ -import { brand } from '@financial-times/n-concept-ids'; +import { brand } from '@financial-times/n-concept-ids' -export default new Map([ - [brand.rachmanReviewPodcast, 'therachmanreview'] -]); +export default new Map([[brand.rachmanReviewPodcast, 'therachmanreview']]) diff --git a/components/x-podcast-launchers/src/copy-to-clipboard.js b/components/x-podcast-launchers/src/copy-to-clipboard.js index 39f476f1b..a8fea460c 100644 --- a/components/x-podcast-launchers/src/copy-to-clipboard.js +++ b/components/x-podcast-launchers/src/copy-to-clipboard.js @@ -1,21 +1,21 @@ -import styles from './PodcastLaunchers.scss'; +import styles from './PodcastLaunchers.scss' -export default function copyToClipboard (event) { - const url = event.target.dataset.url; - const containerEl = event.target.parentElement; - const rssLink = document.createElement('span'); +export default function copyToClipboard(event) { + const url = event.target.dataset.url + const containerEl = event.target.parentElement + const rssLink = document.createElement('span') - rssLink.classList.add(styles.rssUrlCopySpan); - rssLink.appendChild(document.createTextNode(url)); - containerEl.appendChild(rssLink); + rssLink.classList.add(styles.rssUrlCopySpan) + rssLink.appendChild(document.createTextNode(url)) + containerEl.appendChild(rssLink) - const range = document.createRange(); + const range = document.createRange() - window.getSelection().removeAllRanges(); - range.selectNode(rssLink); - window.getSelection().addRange(range); - document.execCommand('copy'); + window.getSelection().removeAllRanges() + range.selectNode(rssLink) + window.getSelection().addRange(range) + document.execCommand('copy') - window.getSelection().removeAllRanges(); - containerEl.removeChild(rssLink); + window.getSelection().removeAllRanges() + containerEl.removeChild(rssLink) } diff --git a/components/x-podcast-launchers/src/generate-app-links.js b/components/x-podcast-launchers/src/generate-app-links.js index 0dae6e651..035a2c03f 100644 --- a/components/x-podcast-launchers/src/generate-app-links.js +++ b/components/x-podcast-launchers/src/generate-app-links.js @@ -1,13 +1,11 @@ -import appLinksConfig from './config/app-links'; +import appLinksConfig from './config/app-links' export default function generateAppLinks(rssUrl) { - return appLinksConfig.map(data => { - const url = data.includeProtocol - ? rssUrl - : rssUrl.replace(/^https?:\/\//, ''); + return appLinksConfig.map((data) => { + const url = data.includeProtocol ? rssUrl : rssUrl.replace(/^https?:\/\//, '') return Object.assign({}, data, { url: data.template.replace(/{url}/, url) - }); - }); + }) + }) } diff --git a/components/x-podcast-launchers/src/generate-rss-url.js b/components/x-podcast-launchers/src/generate-rss-url.js index fb68447ed..d438d68ac 100644 --- a/components/x-podcast-launchers/src/generate-rss-url.js +++ b/components/x-podcast-launchers/src/generate-rss-url.js @@ -1,8 +1,8 @@ -import nanoid from 'nanoid'; +import nanoid from 'nanoid' export default function generateRSSUrl(acastHost, seriesId, token) { // the API needs any token for now, as it is a private RSS feed. // If the experiment successful these tokens would eventually be tied to the user’s account with Membership // ie. https://access.acast.cloud/rss/ft-test/tYPWWHla - return `${acastHost}/rss/${seriesId}/${token || nanoid(10)}`; + return `${acastHost}/rss/${seriesId}/${token || nanoid(10)}` } diff --git a/components/x-podcast-launchers/stories/example.js b/components/x-podcast-launchers/stories/example.js index a88465dd6..6b4e91494 100644 --- a/components/x-podcast-launchers/stories/example.js +++ b/components/x-podcast-launchers/stories/example.js @@ -1,6 +1,6 @@ -const { brand } = require('@financial-times/n-concept-ids'); +const { brand } = require('@financial-times/n-concept-ids') -exports.title = 'Example'; +exports.title = 'Example' exports.data = { conceptId: brand.rachmanReviewPodcast, @@ -9,8 +9,8 @@ exports.data = { csrfToken: 'token', acastRSSHost: 'https://access.acast.com', acastAccessToken: 'abc-123' -}; +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-podcast-launchers/stories/index.js b/components/x-podcast-launchers/stories/index.js index 73f036947..696d54f4c 100644 --- a/components/x-podcast-launchers/stories/index.js +++ b/components/x-podcast-launchers/stories/index.js @@ -1,8 +1,8 @@ -const { PodcastLaunchers } = require('../'); +const { PodcastLaunchers } = require('../') -exports.component = PodcastLaunchers; +exports.component = PodcastLaunchers -exports.package = require('../package.json'); +exports.package = require('../package.json') // Set up basic document styling using the Origami build service exports.dependencies = { @@ -10,10 +10,8 @@ exports.dependencies = { 'o-typography': '^5.5.0', 'o-buttons': '^5.16.6', 'o-forms': '^7.0.0' -}; +} -exports.stories = [ - require('./example') -]; +exports.stories = [require('./example')] -exports.knobs = require('./knobs'); +exports.knobs = require('./knobs') diff --git a/components/x-podcast-launchers/stories/knobs.js b/components/x-podcast-launchers/stories/knobs.js index 6d22e1ba1..4f9ada23e 100644 --- a/components/x-podcast-launchers/stories/knobs.js +++ b/components/x-podcast-launchers/stories/knobs.js @@ -1,5 +1,5 @@ module.exports = (data, { text }) => ({ conceptId: text('Concept id', data.conceptId), acastRSSHost: text('Acast RSS host', data.acastRSSHost), - acastAccessToken: text('Acast Access token', data.acastAccessToken), + acastAccessToken: text('Acast Access token', data.acastAccessToken) }) diff --git a/components/x-privacy-manager/rollup.js b/components/x-privacy-manager/rollup.js index d8f76868e..e389d4123 100644 --- a/components/x-privacy-manager/rollup.js +++ b/components/x-privacy-manager/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/privacy-manager.jsx', pkg }); +xRollup({ input: './src/privacy-manager.jsx', pkg }) diff --git a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx index d5eee87ee..382a6c883 100644 --- a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx +++ b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx @@ -1,10 +1,10 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); -const fetchMock = require('fetch-mock'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') +const fetchMock = require('fetch-mock') -import { BasePrivacyManager, PrivacyManager } from '../privacy-manager'; +import { BasePrivacyManager, PrivacyManager } from '../privacy-manager' -const TEST_CONSENT_URL = 'https://consent.ft.com'; +const TEST_CONSENT_URL = 'https://consent.ft.com' const buildPayload = (consent) => ({ consentSource: 'consuming-app', @@ -14,38 +14,38 @@ const buildPayload = (consent) => ({ fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', lbi: true, source: 'consuming-app', - status: consent, - }, + status: consent + } }, demographicAds: { onsite: { fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', lbi: true, source: 'consuming-app', - status: consent, - }, + status: consent + } }, programmaticAds: { onsite: { fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', lbi: true, source: 'consuming-app', - status: consent, - }, - }, + status: consent + } + } }, cookieDomain: '.ft.com', - formOfWordsId: 'privacyCCPA', -}); + formOfWordsId: 'privacyCCPA' +}) function checkPayload(opts, expected) { - const consents = JSON.parse(String(opts.body)).data; + const consents = JSON.parse(String(opts.body)).data - let worked = true; + let worked = true for (const category in consents) { - worked = worked && consents[category].onsite.status === expected; + worked = worked && consents[category].onsite.status === expected } - return worked; + return worked } const defaultProps = { @@ -56,103 +56,103 @@ const defaultProps = { }, consentSource: 'consuming-app', referrer: 'www.ft.com', - cookieDomain: '.ft.com', -}; + cookieDomain: '.ft.com' +} describe('x-privacy-manager', () => { describe('initial state', () => { beforeEach(() => { - fetchMock.reset(); + fetchMock.reset() const okResponse = { body: { a: 'b' }, - status: 200, - }; - fetchMock.mock(TEST_CONSENT_URL, okResponse, { delay: 500 }); - }); + status: 200 + } + fetchMock.mock(TEST_CONSENT_URL, okResponse, { delay: 500 }) + }) it('defaults to "Allow"', () => { - const subject = mount(<PrivacyManager {...defaultProps} />); - const inputTrue = subject.find('input[value="true"]').first(); + const subject = mount(<PrivacyManager {...defaultProps} />) + const inputTrue = subject.find('input[value="true"]').first() // Verify that initial props are correctly reflected - expect(inputTrue.prop('checked')).toBe(true); - }); + expect(inputTrue.prop('checked')).toBe(true) + }) it('highlights explicitly set consent correctly: false', () => { - const subject = mount(<PrivacyManager {...defaultProps} consent={false} />); - const inputFalse = subject.find('input[value="false"]').first(); + const subject = mount(<PrivacyManager {...defaultProps} consent={false} />) + const inputFalse = subject.find('input[value="false"]').first() // Verify that initial props are correctly reflected - expect(inputFalse.prop('checked')).toBe(true); - }); + expect(inputFalse.prop('checked')).toBe(true) + }) it('highlights explicitly set consent correctly: true', () => { - const subject = mount(<PrivacyManager consent={true} {...defaultProps} />); - const inputTrue = subject.find('input[value="true"]').first(); + const subject = mount(<PrivacyManager consent={true} {...defaultProps} />) + const inputTrue = subject.find('input[value="true"]').first() // Verify that initial props are correctly reflected - expect(inputTrue.prop('checked')).toBe(true); - }); + expect(inputTrue.prop('checked')).toBe(true) + }) it('handles a change of consent', async () => { - const callback1 = jest.fn(); - const callback2 = jest.fn(); - const consentVal = true; + const callback1 = jest.fn() + const callback2 = jest.fn() + const consentVal = true - const props = { ...defaultProps, onConsentSavedCallbacks: [callback1, callback2] }; + const props = { ...defaultProps, onConsentSavedCallbacks: [callback1, callback2] } - const payload = buildPayload(consentVal); + const payload = buildPayload(consentVal) - const subject = mount(<PrivacyManager {...props} />); - const form = subject.find('form').first(); - const inputTrue = subject.find('input[value="true"]').first(); - const inputFalse = subject.find('input[value="false"]').first(); + const subject = mount(<PrivacyManager {...props} />) + const form = subject.find('form').first() + const inputTrue = subject.find('input[value="true"]').first() + const inputFalse = subject.find('input[value="false"]').first() // Switch consent to false and submit form - await inputFalse.prop('onChange')(undefined); - await form.prop('onSubmit')(undefined); + await inputFalse.prop('onChange')(undefined) + await form.prop('onSubmit')(undefined) // Reconcile snapshot with state - subject.update(); + subject.update() // Check that fetch was called with the correct values - expect(checkPayload(fetchMock.lastOptions(), false)).toBe(true); + expect(checkPayload(fetchMock.lastOptions(), false)).toBe(true) // Switch consent back to true and resubmit form - await inputTrue.prop('onChange')(undefined); - await form.prop('onSubmit')(undefined); + await inputTrue.prop('onChange')(undefined) + await form.prop('onSubmit')(undefined) // Check both callbacks were run with `payload` - expect(callback1).toHaveBeenCalledWith(null, { payload, consent: true }); - expect(callback2).toHaveBeenCalledWith(null, { payload, consent: true }); + expect(callback1).toHaveBeenCalledWith(null, { payload, consent: true }) + expect(callback2).toHaveBeenCalledWith(null, { payload, consent: true }) // Reconcile snapshot with state - subject.update(); + subject.update() // Check that fetch was called with the correct values - expect(checkPayload(fetchMock.lastOptions(), true)).toBe(true); + expect(checkPayload(fetchMock.lastOptions(), true)).toBe(true) // Verify that confimatory nmessage is displayed - const message = subject.find('[data-o-component="o-message"]').first(); - const link = message.find('[data-component="referrer-link"]'); - expect(message).toHaveClassName('o-message--success'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - expect(inputTrue).toHaveProp('checked', true); - }); - }); + const message = subject.find('[data-o-component="o-message"]').first() + const link = message.find('[data-component="referrer-link"]') + expect(message).toHaveClassName('o-message--success') + expect(link).toHaveProp('href', 'https://www.ft.com/') + expect(inputTrue).toHaveProp('checked', true) + }) + }) describe('It displays the appropriate messaging', () => { function findMessageComponent(props) { - const subject = mount(<BasePrivacyManager {...props} />); - const messages = subject.find('[data-o-component="o-message"]'); - const message = messages.first(); - const link = message.find('[data-component="referrer-link"]'); + const subject = mount(<BasePrivacyManager {...props} />) + const messages = subject.find('[data-o-component="o-message"]') + const message = messages.first() + const link = message.find('[data-component="referrer-link"]') return { messages, message, - link, - }; + link + } } const messageProps = { @@ -161,49 +161,49 @@ describe('x-privacy-manager', () => { legislation: ['ccpa'], actions: { onConsentChange: jest.fn(() => {}), - sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }), + sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }) }, isLoading: false, - _response: undefined, - }; + _response: undefined + } it('None by default', () => { - const { messages } = findMessageComponent(messageProps); - expect(messages).toHaveLength(0); - }); + const { messages } = findMessageComponent(messageProps) + expect(messages).toHaveLength(0) + }) it('While loading', () => { - const { messages, message } = findMessageComponent({ ...messageProps, isLoading: true }); - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--neutral'); - }); + const { messages, message } = findMessageComponent({ ...messageProps, isLoading: true }) + expect(messages).toHaveLength(1) + expect(message).toHaveClassName('o-message--neutral') + }) it('On receiving a response with a status of 200', () => { - const _response = { ok: true, status: 200 }; - const { messages, message, link } = findMessageComponent({ ...messageProps, _response }); + const _response = { ok: true, status: 200 } + const { messages, message, link } = findMessageComponent({ ...messageProps, _response }) - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--success'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - }); + expect(messages).toHaveLength(1) + expect(message).toHaveClassName('o-message--success') + expect(link).toHaveProp('href', 'https://www.ft.com/') + }) it('On receiving a response with a non-200 status', () => { - const _response = { ok: false, status: 400 }; - const { messages, message, link } = findMessageComponent({ ...messageProps, _response }); + const _response = { ok: false, status: 400 } + const { messages, message, link } = findMessageComponent({ ...messageProps, _response }) - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--error'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - }); + expect(messages).toHaveLength(1) + expect(message).toHaveClassName('o-message--error') + expect(link).toHaveProp('href', 'https://www.ft.com/') + }) it('On receiving any response with referrer undefined', () => { - const _response = { ok: false, status: 400 }; - const referrer = undefined; - const { messages, message, link } = findMessageComponent({ ...messageProps, referrer, _response }); - - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--error'); - expect(link).toHaveLength(0); - }); - }); -}); + const _response = { ok: false, status: 400 } + const referrer = undefined + const { messages, message, link } = findMessageComponent({ ...messageProps, referrer, _response }) + + expect(messages).toHaveLength(1) + expect(message).toHaveClassName('o-message--error') + expect(link).toHaveLength(0) + }) + }) +}) diff --git a/components/x-privacy-manager/src/messages.jsx b/components/x-privacy-manager/src/messages.jsx index b60155efb..31db67d2d 100644 --- a/components/x-privacy-manager/src/messages.jsx +++ b/components/x-privacy-manager/src/messages.jsx @@ -1,5 +1,5 @@ -import { h } from '@financial-times/x-engine'; -import s from './privacy-manager.scss'; +import { h } from '@financial-times/x-engine' +import s from './privacy-manager.scss' /** * Provide a way to return to the referrer's homepage @@ -8,22 +8,22 @@ import s from './privacy-manager.scss'; * @param {string} referrer */ function renderReferrerLink(referrer) { - if (!referrer) return; + if (!referrer) return - let url; + let url try { - url = new URL(`https://${referrer}`); + url = new URL(`https://${referrer}`) } catch (_) { // referrer cannot be parsed: omit link - return; + return } return ( <a href={url.href} data-component="referrer-link" className="o-message__actions__secondary"> Continue to homepage </a> - ); + ) } function Message({ cls, children }) { @@ -35,7 +35,7 @@ function Message({ cls, children }) { </div> </div> </div> - ); + ) } /** @@ -49,32 +49,33 @@ export function ResponseMessage({ success, referrer }) { const statusDict = { true: { cls: 'o-message--success', - msg: 'Your settings have been saved on this device. Please apply your preferences to all of the devices you use to access our Sites.', + msg: + 'Your settings have been saved on this device. Please apply your preferences to all of the devices you use to access our Sites.' }, false: { cls: 'o-message--error', - msg: 'Your settings could not be saved. Please try again later.', - }, - }; + msg: 'Your settings could not be saved. Please try again later.' + } + } - const status = statusDict[success]; - const cls = `o-message o-message--alert ${status.cls}`; + const status = statusDict[success] + const cls = `o-message o-message--alert ${status.cls}` return ( <Message cls={cls}> {status.msg} {renderReferrerLink(referrer)} </Message> - ); + ) } export function LoadingMessage() { - const cls = 'o-message o-message--neutral'; - const spinnerCls = `o-loading o-loading--dark o-loading--small ${s['v-middle']}`; + const cls = 'o-message o-message--neutral' + const spinnerCls = `o-loading o-loading--dark o-loading--small ${s['v-middle']}` return ( <Message cls={cls}> <div className={spinnerCls}></div> <span className={s.loading}>Loading...</span> </Message> - ); + ) } diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx index 47bda957f..dd73744e5 100644 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -1,23 +1,23 @@ /// <reference path="./types.d.ts" /> -import { h } from '@financial-times/x-engine'; -import { withActions } from '@financial-times/x-interaction'; +import { h } from '@financial-times/x-engine' +import { withActions } from '@financial-times/x-interaction' -import s from './privacy-manager.scss'; -import { RadioBtn } from './radio-btn'; -import { LoadingMessage, ResponseMessage } from './messages'; +import s from './privacy-manager.scss' +import { RadioBtn } from './radio-btn' +import { LoadingMessage, ResponseMessage } from './messages' // CCPA legislation doesn't require to record the form-of-words used in the page, // but our consent-proxy schemas do require the field. For that reason, // it was decided to create a placeholder in our FOW database that would always use // for CCPA page independently of the specific wording used in it. // This is the value: -const FOW_NAME = 'privacyCCPA'; -const FOW_VERSION = 'H0IeyQBalorD.6nTqqzhNTKECSgOPJCG'; +const FOW_NAME = 'privacyCCPA' +const FOW_VERSION = 'H0IeyQBalorD.6nTqqzhNTKECSgOPJCG' export const withCustomActions = withActions(() => ({ onConsentChange() { - return ({ consent = true }) => ({ consent: !consent }); + return ({ consent = true }) => ({ consent: !consent }) }, /** @@ -32,16 +32,16 @@ export const withCustomActions = withActions(() => ({ */ sendConsent(consentApiEnhancedUrl, onConsentSavedCallbacks, consentSource, cookieDomain) { return async ({ isLoading, consent }) => { - if (isLoading) return; + if (isLoading) return const categoryPayload = { onsite: { status: consent, lbi: true, source: consentSource, - fow: `${FOW_NAME}/${FOW_VERSION}`, - }, - }; + fow: `${FOW_NAME}/${FOW_VERSION}` + } + } const payload = { formOfWordsId: FOW_NAME, @@ -49,24 +49,24 @@ export const withCustomActions = withActions(() => ({ data: { behaviouralAds: categoryPayload, demographicAds: categoryPayload, - programmaticAds: categoryPayload, - }, - }; + programmaticAds: categoryPayload + } + } if (cookieDomain) { // Optionally specifiy the domain for the cookie consent api will set - payload.cookieDomain = cookieDomain; + payload.cookieDomain = cookieDomain } try { const res = await fetch(consentApiEnhancedUrl, { method: 'POST', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, body: JSON.stringify(payload), - credentials: 'include', - }); + credentials: 'include' + }) // On response call any externally defined handlers following Node's convention: // 1. Either an error object or `null` as the first argument @@ -74,24 +74,24 @@ export const withCustomActions = withActions(() => ({ // Allows callbacks to decide how to handle a failure scenario if (res.ok === false) { - throw new Error(res.statusText || String(res.status)); + throw new Error(res.statusText || String(res.status)) } for (const fn of onConsentSavedCallbacks) { - fn(null, { consent, payload }); + fn(null, { consent, payload }) } - return { _response: { ok: true } }; + return { _response: { ok: true } } } catch (err) { for (const fn of onConsentSavedCallbacks) { - fn(err, { consent, payload }); + fn(err, { consent, payload }) } - return { _response: { ok: false } }; + return { _response: { ok: false } } } - }; - }, -})); + } + } +})) /** * @param {boolean} isLoading @@ -99,9 +99,9 @@ export const withCustomActions = withActions(() => ({ * @param {string} referrer */ function renderMessage(isLoading, response, referrer) { - if (isLoading) return <LoadingMessage />; - if (response) return <ResponseMessage success={response.ok} referrer={referrer} />; - return null; + if (isLoading) return <LoadingMessage /> + if (response) return <ResponseMessage success={response.ok} referrer={referrer} /> + return null } /** @@ -109,14 +109,14 @@ function renderMessage(isLoading, response, referrer) { * @param {string | undefined} userId */ function renderLoggedOutWarning(userId) { - if (userId && userId.length > 0) return null; + if (userId && userId.length > 0) return null return ( <p className={`${s.consent__copy} ${s['consent__copy--cta']}`}> - Please sign into your account before submitting your preferences to ensure these changes are - applied across all of your devices + Please sign into your account before submitting your preferences to ensure these changes are applied + across all of your devices </p> - ); + ) } /** @@ -133,32 +133,31 @@ export function BasePrivacyManager({ cookieDomain, actions, isLoading, - _response = undefined, + _response = undefined }) { - const trackingAction = consent ? 'allow' : 'block'; - const btnTrackingId = `ccpa-advertising-consent-${trackingAction}`; + const trackingAction = consent ? 'allow' : 'block' + const btnTrackingId = `ccpa-advertising-consent-${trackingAction}` return ( <div className={s.consent}> <h1 className={s.consent__title}>Do Not Sell My Personal Information</h1> <div className={s.consent__copy}> <p> - If you are a California resident, the California Consumer Privacy Act (CCPA) provides you - with a right to opt out of the sale of your personal information. The definition of sale - is extremely broad under the CCPA, and may include sharing certain pieces of information - with our advertising partners, such as cookie identifiers, geolocation and interactions - with advertisements, for the purposes of showing you advertising that is relevant to your - interests. You can find more information about this in our{' '} - <a href="https://help.ft.com/legal-privacy/privacy-policy/">Privacy Policy</a>, including + If you are a California resident, the California Consumer Privacy Act (CCPA) provides you with a + right to opt out of the sale of your personal information. The definition of sale is extremely broad + under the CCPA, and may include sharing certain pieces of information with our advertising partners, + such as cookie identifiers, geolocation and interactions with advertisements, for the purposes of + showing you advertising that is relevant to your interests. You can find more information about this + in our <a href="https://help.ft.com/legal-privacy/privacy-policy/">Privacy Policy</a>, including other ways to opt out. </p> <p> - You can choose to block sharing of this data with advertisers. This means that we turn off - some types of advertising based on information you have given us and your use of our - Sites, ensuring that our advertising partners do not receive this data. By opting out, you - will stop receiving adverts that are targeted specifically to you; however, you will still - see the same number of adverts on our Sites. + You can choose to block sharing of this data with advertisers. This means that we turn off some + types of advertising based on information you have given us and your use of our Sites, ensuring that + our advertising partners do not receive this data. By opting out, you will stop receiving adverts + that are targeted specifically to you; however, you will still see the same number of adverts on our + Sites. </p> <hr className={s.divider} /> {renderLoggedOutWarning(userId)} @@ -168,13 +167,13 @@ export function BasePrivacyManager({ <form action={consentProxyEndpoints.createOrUpdateRecord} onSubmit={(event) => { - event && event.preventDefault(); + event && event.preventDefault() return actions.sendConsent( consentProxyEndpoints.createOrUpdateRecord, onConsentSavedCallbacks, consentSource, cookieDomain - ); + ) }}> <div className={s.form__controls}> <RadioBtn @@ -200,9 +199,9 @@ export function BasePrivacyManager({ </form> </div> </div> - ); + ) } -const PrivacyManager = withCustomActions(BasePrivacyManager); +const PrivacyManager = withCustomActions(BasePrivacyManager) -export { PrivacyManager }; +export { PrivacyManager } diff --git a/components/x-privacy-manager/src/radio-btn.jsx b/components/x-privacy-manager/src/radio-btn.jsx index 2e771592e..ac6fbaa06 100644 --- a/components/x-privacy-manager/src/radio-btn.jsx +++ b/components/x-privacy-manager/src/radio-btn.jsx @@ -1,9 +1,9 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' -import s from './radio-btn.scss'; +import s from './radio-btn.scss' export function RadioBtn({ value, trackingId, checked, onChange, children }) { - const id = `ccpa-${value}`; + const id = `ccpa-${value}` return ( <div className={s.control}> @@ -18,9 +18,7 @@ export function RadioBtn({ value, trackingId, checked, onChange, children }) { data-trackable={trackingId} /> <label htmlFor={id} className={s.label}> - <span className={s.label__text}> - {children} - </span> + <span className={s.label__text}>{children}</span> <svg className={s.label__icon} viewBox="0 0 36 36" aria-hidden="true" focusable="false"> <circle className={s.label__icon__outer} cx="18" cy="18" r="16" /> @@ -28,5 +26,5 @@ export function RadioBtn({ value, trackingId, checked, onChange, children }) { </svg> </label> </div> - ); + ) } diff --git a/components/x-privacy-manager/src/types.d.ts b/components/x-privacy-manager/src/types.d.ts index 58339b672..bbb86a230 100644 --- a/components/x-privacy-manager/src/types.d.ts +++ b/components/x-privacy-manager/src/types.d.ts @@ -1,59 +1,53 @@ type CategoryPayload = { onsite: { - status: boolean; - lbi: boolean; - source: string; - fow: string; - }; -}; - -type CCPAConsentData = Record< - 'behaviouralAds' | 'demographicAds' | 'programmaticAds', - CategoryPayload ->; + status: boolean + lbi: boolean + source: string + fow: string + } +} + +type CCPAConsentData = Record<'behaviouralAds' | 'demographicAds' | 'programmaticAds', CategoryPayload> type CCPAConsentPayload = { - formOfWordsId: string; - consentSource: string; - data: CCPAConsentData; -}; + formOfWordsId: string + consentSource: string + data: CCPAConsentData +} -type OnSaveCallback = ( - err: null | Error, - data: { consent: boolean; payload: CCPAConsentPayload } -) => void; +type OnSaveCallback = (err: null | Error, data: { consent: boolean; payload: CCPAConsentPayload }) => void type Actions = { - onConsentChange: () => void; + onConsentChange: () => void sendConsent: ( consentApiUrl: string, onConsentSavedCallbacks: OnSaveCallback[], consentSource: string - ) => Promise<{ _response: _Response }>; -}; + ) => Promise<{ _response: _Response }> +} type _Response = { - ok: boolean; - status?: number; -}; + ok: boolean + status?: number +} -type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string>; +type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string> -type ConsentProxyEndpoints = { [key in keyof ConsentProxyEndpoint]: string }; +type ConsentProxyEndpoints = { [key in keyof ConsentProxyEndpoint]: string } type PrivacyManagerProps = { - userId: string | undefined; - consent?: boolean; - referrer?: string; - cookieDomain?: string; - legislation?: string[]; - consentSource: string; - consentProxyEndpoints: ConsentProxyEndpoints; - onConsentSavedCallbacks?: OnSaveCallback[]; -}; + userId: string | undefined + consent?: boolean + referrer?: string + cookieDomain?: string + legislation?: string[] + consentSource: string + consentProxyEndpoints: ConsentProxyEndpoints + onConsentSavedCallbacks?: OnSaveCallback[] +} type BasePrivacyManagerProps = PrivacyManagerProps & { - actions: Actions; - isLoading?: boolean; - _response: _Response; -}; + actions: Actions + isLoading?: boolean + _response: _Response +} diff --git a/components/x-privacy-manager/storybook/data.js b/components/x-privacy-manager/storybook/data.js index 83a9f8c57..6fd4dd0d8 100644 --- a/components/x-privacy-manager/storybook/data.js +++ b/components/x-privacy-manager/storybook/data.js @@ -1,27 +1,26 @@ -const CONSENT_API = 'https://consent.ft.com'; +const CONSENT_API = 'https://consent.ft.com' const defaults = { - userId: "fakeUserId", + userId: 'fakeUserId', consent: true, legislation: [], referrer: 'ft.com', consentProxyEndpoints: { core: CONSENT_API, enhanced: CONSENT_API, - createOrUpdateRecord: CONSENT_API, - }, -}; + createOrUpdateRecord: CONSENT_API + } +} const getFetchMock = (status = 200, options = {}) => (fetchMock) => { - fetchMock.mock(CONSENT_API, status, { delay: 1000, - ...options, - }); -}; + ...options + }) +} module.exports = { CONSENT_API, defaults, - getFetchMock, -}; + getFetchMock +} diff --git a/components/x-privacy-manager/storybook/index.js b/components/x-privacy-manager/storybook/index.js index 8cba31024..4146012ba 100644 --- a/components/x-privacy-manager/storybook/index.js +++ b/components/x-privacy-manager/storybook/index.js @@ -1,20 +1,20 @@ -const { PrivacyManager } = require('../src/privacy-manager'); +const { PrivacyManager } = require('../src/privacy-manager') -exports.component = PrivacyManager; +exports.component = PrivacyManager -exports.package = require('../package.json'); +exports.package = require('../package.json') exports.dependencies = { 'o-loading': '^4.0.0', 'o-message': '^4.0.0', - 'o-typography': '^6.0.0', -}; + 'o-typography': '^6.0.0' +} exports.stories = [ require('./story-consent-indeterminate'), require('./story-consent-accepted'), require('./story-consent-blocked'), - require('./story-save-failed'), -]; + require('./story-save-failed') +] -exports.knobs = require('./knobs'); +exports.knobs = require('./knobs') diff --git a/components/x-privacy-manager/storybook/knobs.js b/components/x-privacy-manager/storybook/knobs.js index 8e2487cb8..a4adc773b 100644 --- a/components/x-privacy-manager/storybook/knobs.js +++ b/components/x-privacy-manager/storybook/knobs.js @@ -1,7 +1,7 @@ const legislation = { - CCPA: ['ccpa', 'gdpr'], + CCPA: ['ccpa', 'gdpr'] // GDPR: ['gdpr'] -}; +} const referrers = { 'ft.com': 'www.ft.com', @@ -19,20 +19,20 @@ const referrers = { 'pwmnet.com': 'www.pwmnet.com', 'thebanker.com': 'www.thebanker.com', 'thebankerdatabase.com': 'www.thebankerdatabase.com', - Undefined: '', -}; + Undefined: '' +} module.exports = (data, { boolean, select }) => ({ userId() { - return select('Authenticated', { loggedIn: data.userId, loggedOut: undefined }); + return select('Authenticated', { loggedIn: data.userId, loggedOut: undefined }) }, consent() { - return boolean('Consent', data.consent, undefined); + return boolean('Consent', data.consent, undefined) }, legislation() { - return select('Legislation', legislation, legislation['CCPA']); + return select('Legislation', legislation, legislation['CCPA']) }, referrer() { - return select('Referrer', referrers, referrers['ft.com']); - }, -}); + return select('Referrer', referrers, referrers['ft.com']) + } +}) diff --git a/components/x-privacy-manager/storybook/story-consent-accepted.js b/components/x-privacy-manager/storybook/story-consent-accepted.js index 6c916230d..69a3b1b2e 100644 --- a/components/x-privacy-manager/storybook/story-consent-accepted.js +++ b/components/x-privacy-manager/storybook/story-consent-accepted.js @@ -1,16 +1,16 @@ -const { defaults, getFetchMock } = require('./data'); +const { defaults, getFetchMock } = require('./data') -exports.title = 'Consent: accepted'; +exports.title = 'Consent: accepted' exports.data = { ...defaults, consent: true -}; +} -exports.knobs = Object.keys(exports.data); +exports.knobs = Object.keys(exports.data) -exports.fetchMock = getFetchMock(); +exports.fetchMock = getFetchMock() // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-privacy-manager/storybook/story-consent-blocked.js b/components/x-privacy-manager/storybook/story-consent-blocked.js index ed9c79c13..f6d68ec5f 100644 --- a/components/x-privacy-manager/storybook/story-consent-blocked.js +++ b/components/x-privacy-manager/storybook/story-consent-blocked.js @@ -1,16 +1,16 @@ -const { defaults, getFetchMock } = require('./data'); +const { defaults, getFetchMock } = require('./data') -exports.title = 'Consent: blocked'; +exports.title = 'Consent: blocked' exports.data = { ...defaults, consent: false -}; +} -exports.knobs = Object.keys(exports.data); +exports.knobs = Object.keys(exports.data) -exports.fetchMock = getFetchMock(); +exports.fetchMock = getFetchMock() // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-privacy-manager/storybook/story-consent-indeterminate.js b/components/x-privacy-manager/storybook/story-consent-indeterminate.js index e042406fc..e93495bfe 100644 --- a/components/x-privacy-manager/storybook/story-consent-indeterminate.js +++ b/components/x-privacy-manager/storybook/story-consent-indeterminate.js @@ -1,16 +1,16 @@ -const { defaults, getFetchMock } = require('./data'); +const { defaults, getFetchMock } = require('./data') -exports.title = 'Consent: indeterminate'; +exports.title = 'Consent: indeterminate' exports.data = { ...defaults, consent: undefined -}; +} -exports.knobs = Object.keys(exports.data); +exports.knobs = Object.keys(exports.data) -exports.fetchMock = getFetchMock(); +exports.fetchMock = getFetchMock() // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-privacy-manager/storybook/story-save-failed.js b/components/x-privacy-manager/storybook/story-save-failed.js index 89e6cc548..ecbf445ae 100644 --- a/components/x-privacy-manager/storybook/story-save-failed.js +++ b/components/x-privacy-manager/storybook/story-save-failed.js @@ -1,13 +1,13 @@ -const { defaults, getFetchMock } = require('./data'); +const { defaults, getFetchMock } = require('./data') -exports.title = 'Save failed'; +exports.title = 'Save failed' -exports.data = defaults; +exports.data = defaults -exports.knobs = Object.keys(exports.data); +exports.knobs = Object.keys(exports.data) -exports.fetchMock = getFetchMock(500); +exports.fetchMock = getFetchMock(500) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-styling-demo/rollup.js b/components/x-styling-demo/rollup.js index ffd6fd551..07750437c 100644 --- a/components/x-styling-demo/rollup.js +++ b/components/x-styling-demo/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/Button.jsx', pkg }); +xRollup({ input: './src/Button.jsx', pkg }) diff --git a/components/x-styling-demo/src/Button.jsx b/components/x-styling-demo/src/Button.jsx index f1c56e107..fe460605e 100644 --- a/components/x-styling-demo/src/Button.jsx +++ b/components/x-styling-demo/src/Button.jsx @@ -1,13 +1,13 @@ -import { h } from '@financial-times/x-engine'; -import buttonStyles from './Button.css'; -import classNames from 'classnames'; +import { h } from '@financial-times/x-engine' +import buttonStyles from './Button.css' +import classNames from 'classnames' -export const Button = ({large, danger}) => <button - className={classNames( - buttonStyles.button, - { +export const Button = ({ large, danger }) => ( + <button + className={classNames(buttonStyles.button, { [buttonStyles.large]: large, - [buttonStyles.danger]: danger, - } - )} ->Click me!</button>; + [buttonStyles.danger]: danger + })}> + Click me! + </button> +) diff --git a/components/x-styling-demo/stories/index.js b/components/x-styling-demo/stories/index.js index 7e672112d..525aeeb0f 100644 --- a/components/x-styling-demo/stories/index.js +++ b/components/x-styling-demo/stories/index.js @@ -1,17 +1,15 @@ -const { Button } = require('../'); +const { Button } = require('../') -exports.component = Button; -exports.package = require('../package.json'); -exports.stories = [ - require('./styling'), -]; +exports.component = Button +exports.package = require('../package.json') +exports.stories = [require('./styling')] exports.knobs = (data, { boolean }) => ({ danger() { - return boolean('Danger', data.danger); + return boolean('Danger', data.danger) }, large() { - return boolean('Large', data.large); + return boolean('Large', data.large) } -}); +}) diff --git a/components/x-styling-demo/stories/styling.js b/components/x-styling-demo/stories/styling.js index 2df396b11..3a0f07633 100644 --- a/components/x-styling-demo/stories/styling.js +++ b/components/x-styling-demo/stories/styling.js @@ -1,15 +1,12 @@ -exports.title = 'Styling'; +exports.title = 'Styling' exports.data = { danger: false, - large: false, -}; + large: false +} -exports.knobs = [ - 'danger', - 'large', -]; +exports.knobs = ['danger', 'large'] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx index a0d163e26..53e04e9e0 100644 --- a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -1,142 +1,141 @@ -const renderer = require('react-test-renderer'); -const { h } = require('@financial-times/x-engine'); -const { shallow } = require('@financial-times/x-test-utils/enzyme'); -const contentItems = require('../stories/content-items.json'); +const renderer = require('react-test-renderer') +const { h } = require('@financial-times/x-engine') +const { shallow } = require('@financial-times/x-test-utils/enzyme') +const contentItems = require('../stories/content-items.json') -const { TeaserTimeline } = require('../'); +const { TeaserTimeline } = require('../') describe('x-teaser-timeline', () => { - let props; - let tree; + let props + let tree beforeEach(() => { props = { items: contentItems, timezoneOffset: -60, localTodayDate: '2018-10-17' - }; - }); + } + }) describe('given latestItemsTime is set', () => { beforeEach(() => { - tree = renderer.create(<TeaserTimeline - {...props} - latestItemsTime='2018-10-17T12:10:33.000Z' - />).toJSON(); - }); + tree = renderer + .create(<TeaserTimeline {...props} latestItemsTime="2018-10-17T12:10:33.000Z" />) + .toJSON() + }) it('renders latest, earlier, yesterday and October 15th item groups', () => { - expect(tree).toMatchSnapshot(); - }); - }); + expect(tree).toMatchSnapshot() + }) + }) describe('given latestItemsTime is set and results in all today`s articles being "latest"', () => { - let component; + let component beforeEach(() => { component = shallow( - <TeaserTimeline - {...props} - timezoneOffset={0} - latestItemsTime='2018-10-17T00:00:00.000Z' - />); - }); + <TeaserTimeline {...props} timezoneOffset={0} latestItemsTime="2018-10-17T00:00:00.000Z" /> + ) + }) it('does not render the empty "earlier today" group', () => { - expect(component.render().find('section')).toHaveLength(3); - expect(component.render().find('section h2').text().toLowerCase().includes('earlier today')).toBe(false); - }); - }); + expect(component.render().find('section')).toHaveLength(3) + expect(component.render().find('section h2').text().toLowerCase().includes('earlier today')).toBe(false) + }) + }) describe('given latestItemsTime is set and results in all today\'s and some of yesterday\'s articles being "latest"', () => { beforeEach(() => { - tree = renderer.create(<TeaserTimeline - {...props} - timezoneOffset={0} - latestItemsTime='2018-10-16T11:59:59.999Z' - latestItemsAgeHours={36} - />).toJSON(); - }); + tree = renderer + .create( + <TeaserTimeline + {...props} + timezoneOffset={0} + latestItemsTime="2018-10-16T11:59:59.999Z" + latestItemsAgeHours={36} + /> + ) + .toJSON() + }) it('renders latest, yesterday and October 15th item groups (no earlier today)', () => { - expect(tree).toMatchSnapshot(); - }); - }); + expect(tree).toMatchSnapshot() + }) + }) describe('given latestItemsTime is not set', () => { beforeEach(() => { - tree = renderer.create(<TeaserTimeline {...props} />).toJSON(); - }); + tree = renderer.create(<TeaserTimeline {...props} />).toJSON() + }) it('renders earlier, yesterday and October 15th item groups (no latest)', () => { - expect(tree).toMatchSnapshot(); - }); - }); + expect(tree).toMatchSnapshot() + }) + }) describe('given latestItemsTime is set but is more than latestItemsAgeHours ago', () => { beforeEach(() => { - tree = renderer.create(<TeaserTimeline - {...props} - latestItemsTime='2018-10-15T11:59:59.999Z' - latestItemsAgeHours={36} - />).toJSON(); - }); + tree = renderer + .create( + <TeaserTimeline {...props} latestItemsTime="2018-10-15T11:59:59.999Z" latestItemsAgeHours={36} /> + ) + .toJSON() + }) it('ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest)', () => { - expect(tree).toMatchSnapshot(); - }); - }); + expect(tree).toMatchSnapshot() + }) + }) describe('given latestItemsTime is set but is not same date as localTodayDate', () => { beforeEach(() => { - tree = renderer.create(<TeaserTimeline - {...props} - latestItemsTime='2018-10-16T12:10:33.000Z' - />).toJSON(); - }); + tree = renderer + .create(<TeaserTimeline {...props} latestItemsTime="2018-10-16T12:10:33.000Z" />) + .toJSON() + }) it('ignores latestItemsTime and renders earlier, yesterday and October 15th item groups (no latest)', () => { - expect(tree).toMatchSnapshot(); - }); - }); + expect(tree).toMatchSnapshot() + }) + }) describe('showSaveButtons', () => { describe('given showSaveButtons is not set or is true', () => { beforeEach(() => { - tree = renderer.create(<TeaserTimeline {...props} />).toJSON(); - }); + tree = renderer.create(<TeaserTimeline {...props} />).toJSON() + }) it('renders save buttons by default', () => { - expect(tree).toMatchSnapshot(); - }); - }); + expect(tree).toMatchSnapshot() + }) + }) describe('given showSaveButtons is set to false', () => { beforeEach(() => { - tree = renderer.create(<TeaserTimeline {...props} showSaveButtons={false} />).toJSON(); - }); + tree = renderer.create(<TeaserTimeline {...props} showSaveButtons={false} />).toJSON() + }) it('does not render the save buttons', () => { - expect(tree).toMatchSnapshot(); - }); - }); - }); + expect(tree).toMatchSnapshot() + }) + }) + }) describe('given no item are provided', () => { - let component; + let component beforeEach(() => { - delete props.items; - component = shallow(<TeaserTimeline {...props} />); - }); + delete props.items + component = shallow(<TeaserTimeline {...props} />) + }) it('should render nothing', () => { - expect(component.html()).toEqual(null); + expect(component.html()).toEqual(null) }) - }); + }) describe('custom slot', () => { - let component; + let component describe('custom slot content is a string', () => { describe('without latestArticlesTime set', () => { @@ -147,14 +146,14 @@ describe('x-teaser-timeline', () => { customSlotContent='<div class="custom-slot">Custom slot content</div>' customSlotPosition={3} /> - ); - }); + ) + }) it('has custom content in correct position', () => { - expect(component.render().find('.custom-slot')).toHaveLength(1); - expect(component.render().find('li').eq(3).find('.custom-slot')).toHaveLength(1); - }); - }); + expect(component.render().find('.custom-slot')).toHaveLength(1) + expect(component.render().find('li').eq(3).find('.custom-slot')).toHaveLength(1) + }) + }) describe('with latestArticlesTime set', () => { beforeEach(() => { @@ -163,17 +162,17 @@ describe('x-teaser-timeline', () => { {...props} customSlotContent='<div class="custom-slot">Custom slot content</div>' customSlotPosition={2} - latestArticlesTime='2018-10-16T12:10:33.000Z' + latestArticlesTime="2018-10-16T12:10:33.000Z" /> - ); - }); + ) + }) it('has custom content in correct position', () => { - expect(component.render().find('.custom-slot')).toHaveLength(1); - expect(component.render().find('li').eq(2).find('.custom-slot')).toHaveLength(1); - }); - }); - }); + expect(component.render().find('.custom-slot')).toHaveLength(1) + expect(component.render().find('li').eq(2).find('.custom-slot')).toHaveLength(1) + }) + }) + }) describe('custom slot content is a node', () => { beforeEach(() => { @@ -183,13 +182,13 @@ describe('x-teaser-timeline', () => { customSlotContent={<div className="custom-slot">Custom slot content</div>} customSlotPosition={3} /> - ); - }); + ) + }) it('has custom content in correct position', () => { - expect(component.render().find('.custom-slot')).toHaveLength(1); - expect(component.render().find('li').eq(3).find('.custom-slot')).toHaveLength(1); - }); - }); - }); -}); + expect(component.render().find('.custom-slot')).toHaveLength(1) + expect(component.render().find('li').eq(3).find('.custom-slot')).toHaveLength(1) + }) + }) + }) +}) diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js index 52ac433dd..df4d9150a 100644 --- a/components/x-teaser-timeline/__tests__/lib/transform.test.js +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -1,166 +1,165 @@ -import { buildModel } from '../../src/lib/transform'; +import { buildModel } from '../../src/lib/transform' const items = [ { - 'id': '01f0b004-36b9-11ea-a6d3-9a26f8c3cba4', - 'title': 'Europeans step up pressure on Iran over nuclear deal', - 'publishedDate': '2020-01-14T11:10:26.000Z' + id: '01f0b004-36b9-11ea-a6d3-9a26f8c3cba4', + title: 'Europeans step up pressure on Iran over nuclear deal', + publishedDate: '2020-01-14T11:10:26.000Z' }, { - 'id': '01eaf2fc-36ac-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran’s judiciary threatens to expel UK ambassador', - 'publishedDate': '2020-01-14T10:23:14.000Z' + id: '01eaf2fc-36ac-11ea-a6d3-9a26f8c3cba4', + title: 'Iran’s judiciary threatens to expel UK ambassador', + publishedDate: '2020-01-14T10:23:14.000Z' }, { - 'id': 'dcac61ea-361c-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran’s regime loses the battle for public opinion', - 'publishedDate': '2020-01-14T10:00:27.000Z' + id: 'dcac61ea-361c-11ea-a6d3-9a26f8c3cba4', + title: 'Iran’s regime loses the battle for public opinion', + publishedDate: '2020-01-14T10:00:27.000Z' }, { - 'id': 'bf0752ee-3685-11ea-a6d3-9a26f8c3cba4', - 'title': 'Justin Trudeau partly blames US for Iran plane crash', - 'publishedDate': '2020-01-14T08:15:05.000Z' + id: 'bf0752ee-3685-11ea-a6d3-9a26f8c3cba4', + title: 'Justin Trudeau partly blames US for Iran plane crash', + publishedDate: '2020-01-14T08:15:05.000Z' }, { - 'id': '1d1527fa-356c-11ea-a6d3-9a26f8c3cba4', - 'title': 'Biden and Sanders reprise Iraq war fight in 2020 race', - 'publishedDate': '2020-01-13T11:00:26.000Z' + id: '1d1527fa-356c-11ea-a6d3-9a26f8c3cba4', + title: 'Biden and Sanders reprise Iraq war fight in 2020 race', + publishedDate: '2020-01-13T11:00:26.000Z' }, { - 'id': '6524b530-355b-11ea-a6d3-9a26f8c3cba4', - 'title': 'Esper ‘didn’t see’ specific evidence of embassy threat', - 'publishedDate': '2020-01-12T18:36:39.000Z' + id: '6524b530-355b-11ea-a6d3-9a26f8c3cba4', + title: 'Esper ‘didn’t see’ specific evidence of embassy threat', + publishedDate: '2020-01-12T18:36:39.000Z' }, { - 'id': '86df67f6-3524-11ea-a6d3-9a26f8c3cba4', - 'title': 'Lies over air crash shake Iran’s trust in its rulers', - 'publishedDate': '2020-01-12T16:29:11.000Z' + id: '86df67f6-3524-11ea-a6d3-9a26f8c3cba4', + title: 'Lies over air crash shake Iran’s trust in its rulers', + publishedDate: '2020-01-12T16:29:11.000Z' }, { - 'id': '37084c8c-3508-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran questions Revolutionary Guard over downing of airliner', - 'publishedDate': '2020-01-12T09:51:13.000Z' + id: '37084c8c-3508-11ea-a6d3-9a26f8c3cba4', + title: 'Iran questions Revolutionary Guard over downing of airliner', + publishedDate: '2020-01-12T09:51:13.000Z' }, { - 'id': 'b931cc22-3073-11ea-a329-0bcf87a328f2', - 'title': 'What next for oil as US-Iran tensions simmer?', - 'publishedDate': '2020-01-12T09:00:26.000Z' + id: 'b931cc22-3073-11ea-a329-0bcf87a328f2', + title: 'What next for oil as US-Iran tensions simmer?', + publishedDate: '2020-01-12T09:00:26.000Z' }, { - 'id': '0e76e39a-3428-11ea-9703-eea0cae3f0de', - 'title': 'Iran admits it shot down Ukrainian jet', - 'publishedDate': '2020-01-12T05:35:29.000Z' + id: '0e76e39a-3428-11ea-9703-eea0cae3f0de', + title: 'Iran admits it shot down Ukrainian jet', + publishedDate: '2020-01-12T05:35:29.000Z' } -]; +] const groupedItems = [ { - 'date': '2020-01-14', - 'title': 'Earlier Today', - 'items': [ + date: '2020-01-14', + title: 'Earlier Today', + items: [ { - 'articleIndex': 0, - 'localisedLastUpdated': '2020-01-14T11:10:26.000+00:00', - 'id': '01f0b004-36b9-11ea-a6d3-9a26f8c3cba4', - 'title': 'Europeans step up pressure on Iran over nuclear deal', - 'publishedDate': '2020-01-14T11:10:26.000Z' + articleIndex: 0, + localisedLastUpdated: '2020-01-14T11:10:26.000+00:00', + id: '01f0b004-36b9-11ea-a6d3-9a26f8c3cba4', + title: 'Europeans step up pressure on Iran over nuclear deal', + publishedDate: '2020-01-14T11:10:26.000Z' }, { - 'articleIndex': 1, - 'localisedLastUpdated': '2020-01-14T10:23:14.000+00:00', - 'id': '01eaf2fc-36ac-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran’s judiciary threatens to expel UK ambassador', - 'publishedDate': '2020-01-14T10:23:14.000Z' + articleIndex: 1, + localisedLastUpdated: '2020-01-14T10:23:14.000+00:00', + id: '01eaf2fc-36ac-11ea-a6d3-9a26f8c3cba4', + title: 'Iran’s judiciary threatens to expel UK ambassador', + publishedDate: '2020-01-14T10:23:14.000Z' }, { - 'articleIndex': 2, - 'localisedLastUpdated': '2020-01-14T10:00:27.000+00:00', - 'id': 'dcac61ea-361c-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran’s regime loses the battle for public opinion', - 'publishedDate': '2020-01-14T10:00:27.000Z' + articleIndex: 2, + localisedLastUpdated: '2020-01-14T10:00:27.000+00:00', + id: 'dcac61ea-361c-11ea-a6d3-9a26f8c3cba4', + title: 'Iran’s regime loses the battle for public opinion', + publishedDate: '2020-01-14T10:00:27.000Z' }, { - 'articleIndex': 3, - 'localisedLastUpdated': '2020-01-14T08:15:05.000+00:00', - 'id': 'bf0752ee-3685-11ea-a6d3-9a26f8c3cba4', - 'title': 'Justin Trudeau partly blames US for Iran plane crash', - 'publishedDate': '2020-01-14T08:15:05.000Z' + articleIndex: 3, + localisedLastUpdated: '2020-01-14T08:15:05.000+00:00', + id: 'bf0752ee-3685-11ea-a6d3-9a26f8c3cba4', + title: 'Justin Trudeau partly blames US for Iran plane crash', + publishedDate: '2020-01-14T08:15:05.000Z' } ] }, { - 'date': '2020-01-13', - 'title': 'Yesterday', - 'items': - [ - { - 'articleIndex': 4, - 'localisedLastUpdated': '2020-01-13T11:00:26.000+00:00', - 'id': '1d1527fa-356c-11ea-a6d3-9a26f8c3cba4', - 'title': 'Biden and Sanders reprise Iraq war fight in 2020 race', - 'publishedDate': '2020-01-13T11:00:26.000Z' - } - ] + date: '2020-01-13', + title: 'Yesterday', + items: [ + { + articleIndex: 4, + localisedLastUpdated: '2020-01-13T11:00:26.000+00:00', + id: '1d1527fa-356c-11ea-a6d3-9a26f8c3cba4', + title: 'Biden and Sanders reprise Iraq war fight in 2020 race', + publishedDate: '2020-01-13T11:00:26.000Z' + } + ] }, { - 'date': '2020-01-12', - 'title': 'January 12, 2020', - 'items': [ + date: '2020-01-12', + title: 'January 12, 2020', + items: [ { - 'articleIndex': 5, - 'localisedLastUpdated': '2020-01-12T18:36:39.000+00:00', - 'id': '6524b530-355b-11ea-a6d3-9a26f8c3cba4', - 'title': 'Esper ‘didn’t see’ specific evidence of embassy threat', - 'publishedDate': '2020-01-12T18:36:39.000Z' + articleIndex: 5, + localisedLastUpdated: '2020-01-12T18:36:39.000+00:00', + id: '6524b530-355b-11ea-a6d3-9a26f8c3cba4', + title: 'Esper ‘didn’t see’ specific evidence of embassy threat', + publishedDate: '2020-01-12T18:36:39.000Z' }, { - 'articleIndex': 6, - 'localisedLastUpdated': '2020-01-12T16:29:11.000+00:00', - 'id': '86df67f6-3524-11ea-a6d3-9a26f8c3cba4', - 'title': 'Lies over air crash shake Iran’s trust in its rulers', - 'publishedDate': '2020-01-12T16:29:11.000Z' + articleIndex: 6, + localisedLastUpdated: '2020-01-12T16:29:11.000+00:00', + id: '86df67f6-3524-11ea-a6d3-9a26f8c3cba4', + title: 'Lies over air crash shake Iran’s trust in its rulers', + publishedDate: '2020-01-12T16:29:11.000Z' }, { - 'articleIndex': 7, - 'localisedLastUpdated': '2020-01-12T09:51:13.000+00:00', - 'id': '37084c8c-3508-11ea-a6d3-9a26f8c3cba4', - 'title': 'Iran questions Revolutionary Guard over downing of airliner', - 'publishedDate': '2020-01-12T09:51:13.000Z' + articleIndex: 7, + localisedLastUpdated: '2020-01-12T09:51:13.000+00:00', + id: '37084c8c-3508-11ea-a6d3-9a26f8c3cba4', + title: 'Iran questions Revolutionary Guard over downing of airliner', + publishedDate: '2020-01-12T09:51:13.000Z' }, { - 'articleIndex': 8, - 'localisedLastUpdated': '2020-01-12T09:00:26.000+00:00', - 'id': 'b931cc22-3073-11ea-a329-0bcf87a328f2', - 'title': 'What next for oil as US-Iran tensions simmer?', - 'publishedDate': '2020-01-12T09:00:26.000Z' + articleIndex: 8, + localisedLastUpdated: '2020-01-12T09:00:26.000+00:00', + id: 'b931cc22-3073-11ea-a329-0bcf87a328f2', + title: 'What next for oil as US-Iran tensions simmer?', + publishedDate: '2020-01-12T09:00:26.000Z' }, { - 'articleIndex': 9, - 'localisedLastUpdated': '2020-01-12T05:35:29.000+00:00', - 'id': '0e76e39a-3428-11ea-9703-eea0cae3f0de', - 'title': 'Iran admits it shot down Ukrainian jet', - 'publishedDate': '2020-01-12T05:35:29.000Z' + articleIndex: 9, + localisedLastUpdated: '2020-01-12T05:35:29.000+00:00', + id: '0e76e39a-3428-11ea-9703-eea0cae3f0de', + title: 'Iran admits it shot down Ukrainian jet', + publishedDate: '2020-01-12T05:35:29.000Z' } ] } -]; +] describe('buildModel', () => { describe('without custom slot content', () => { test('correctly builds model', () => { - const result = buildModel({ items, timezoneOffset: 0, localTodayDate: '2020-01-14' }); - expect(result).toEqual(groupedItems); - }); - }); + const result = buildModel({ items, timezoneOffset: 0, localTodayDate: '2020-01-14' }) + expect(result).toEqual(groupedItems) + }) + }) describe('with latestItemsTime today', () => { - test('correctly builds model with today\'s group split', () => { + test("correctly builds model with today's group split", () => { const result = buildModel({ items, timezoneOffset: 0, localTodayDate: '2020-01-14', latestItemsTime: '2020-01-14T10:00:00+00:00' - }); + }) expect(result).toEqual([ { date: 'today-latest', @@ -174,9 +173,9 @@ describe('buildModel', () => { }, groupedItems[1], groupedItems[2] - ]); - }); - }); + ]) + }) + }) describe('with latestItemsTime less than latestItemsAgeHours ago', () => { test('correctly builds model with latest items split out', () => { @@ -186,13 +185,13 @@ describe('buildModel', () => { localTodayDate: '2020-01-14', latestItemsTime: '2020-01-13T10:00:00+00:00', latestItemsAgeHours: 36 - }); - expect(result.length).toBe(2); // latest news, 2020-01-12 - expect(result[0].date).toBe('today-latest'); - expect(result[1].date).toBe('2020-01-12'); + }) + expect(result.length).toBe(2) // latest news, 2020-01-12 + expect(result[0].date).toBe('today-latest') + expect(result[1].date).toBe('2020-01-12') - expect(result[0].title).toBe('Latest News'); - expect(result[1].title).toBe('January 12, 2020'); + expect(result[0].title).toBe('Latest News') + expect(result[1].title).toBe('January 12, 2020') expect(result[0].items).toEqual([ groupedItems[0].items[0], @@ -200,10 +199,10 @@ describe('buildModel', () => { groupedItems[0].items[2], groupedItems[0].items[3], groupedItems[1].items[0] - ]); - expect(result[1]).toEqual(groupedItems[2]); - }); - }); + ]) + expect(result[1]).toEqual(groupedItems[2]) + }) + }) describe('with latestItemsTime over latestItemsAgeHours ago', () => { test('builds model without latest items', () => { @@ -213,10 +212,10 @@ describe('buildModel', () => { localTodayDate: '2020-01-14', latestItemsTime: '2020-01-12T11:59:59+00:00', latestItemsAgeHours: 36 - }); - expect(result).toEqual(groupedItems); - }); - }); + }) + expect(result).toEqual(groupedItems) + }) + }) describe('with custom slot content', () => { test('returns correct model for custom slot in middle of first group', () => { @@ -226,16 +225,22 @@ describe('buildModel', () => { localTodayDate: '2020-01-14', customSlotContent: { foo: 1 }, customSlotPosition: 2 - }); + }) expect(result).toEqual([ { ...groupedItems[0], - items: [groupedItems[0].items[0], groupedItems[0].items[1], { foo: 1 }, groupedItems[0].items[2], groupedItems[0].items[3]] + items: [ + groupedItems[0].items[0], + groupedItems[0].items[1], + { foo: 1 }, + groupedItems[0].items[2], + groupedItems[0].items[3] + ] }, groupedItems[1], groupedItems[2] - ]); - }); + ]) + }) test('returns correct model for custom slot at end of second group', () => { const result = buildModel({ items, @@ -243,7 +248,7 @@ describe('buildModel', () => { localTodayDate: '2020-01-14', customSlotContent: { foo: 1 }, customSlotPosition: 5 - }); + }) expect(result).toEqual([ groupedItems[0], { @@ -251,8 +256,8 @@ describe('buildModel', () => { items: [groupedItems[1].items[0], { foo: 1 }] }, groupedItems[2] - ]); - }); + ]) + }) test('returns correct model for custom slot off end of all groups', () => { const result = buildModel({ items, @@ -260,7 +265,7 @@ describe('buildModel', () => { localTodayDate: '2020-01-14', customSlotContent: { foo: 1 }, customSlotPosition: 10 - }); + }) expect(result).toEqual([ groupedItems[0], groupedItems[1], @@ -268,8 +273,8 @@ describe('buildModel', () => { ...groupedItems[2], items: [...groupedItems[2].items, { foo: 1 }] } - ]); - }); + ]) + }) test('returns correct model for custom slot in position 0', () => { const result = buildModel({ items, @@ -277,15 +282,21 @@ describe('buildModel', () => { localTodayDate: '2020-01-14', customSlotContent: { foo: 1 }, customSlotPosition: 0 - }); + }) expect(result).toEqual([ { ...groupedItems[0], - items: [{ foo: 1 }, groupedItems[0].items[0], groupedItems[0].items[1], groupedItems[0].items[2], groupedItems[0].items[3]] + items: [ + { foo: 1 }, + groupedItems[0].items[0], + groupedItems[0].items[1], + groupedItems[0].items[2], + groupedItems[0].items[3] + ] }, groupedItems[1], groupedItems[2] - ]); - }); - }); -}); + ]) + }) + }) +}) diff --git a/components/x-teaser-timeline/rollup.js b/components/x-teaser-timeline/rollup.js index 1b358fef1..0483c1c2e 100644 --- a/components/x-teaser-timeline/rollup.js +++ b/components/x-teaser-timeline/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/TeaserTimeline.jsx', pkg }); +xRollup({ input: './src/TeaserTimeline.jsx', pkg }) diff --git a/components/x-teaser-timeline/src/TeaserTimeline.jsx b/components/x-teaser-timeline/src/TeaserTimeline.jsx index 76282c9e8..676e3bc68 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.jsx +++ b/components/x-teaser-timeline/src/TeaserTimeline.jsx @@ -1,13 +1,13 @@ -import { h } from '@financial-times/x-engine'; -import { ArticleSaveButton } from '@financial-times/x-article-save-button'; -import { Teaser, presets } from '@financial-times/x-teaser'; -import { buildModel } from './lib/transform'; -import { getDateOnly } from './lib/date'; -import styles from './TeaserTimeline.scss'; -import classNames from 'classnames'; +import { h } from '@financial-times/x-engine' +import { ArticleSaveButton } from '@financial-times/x-article-save-button' +import { Teaser, presets } from '@financial-times/x-teaser' +import { buildModel } from './lib/transform' +import { getDateOnly } from './lib/date' +import styles from './TeaserTimeline.scss' +import classNames from 'classnames' -const TeaserTimeline = props => { - const now = new Date(); +const TeaserTimeline = (props) => { + const now = new Date() const { csrfToken = null, showSaveButtons = true, @@ -18,48 +18,55 @@ const TeaserTimeline = props => { localTodayDate = getDateOnly(now.toISOString()), latestItemsTime, latestItemsAgeHours - } = props; + } = props - const itemGroups = buildModel({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}); + const itemGroups = buildModel({ + items, + customSlotContent, + customSlotPosition, + timezoneOffset, + localTodayDate, + latestItemsTime, + latestItemsAgeHours + }) - return itemGroups.length > 0 && ( - <div> - {itemGroups.map(group => ( - <section key={group.date} className={classNames(styles.itemGroup)}> - <h2 className={classNames(styles.itemGroup__heading)}>{group.title}</h2> - <ul className={classNames(styles.itemGroup__items)}> - {group.items.map(item => { - if (item.id) { - return ( - <li key={item.id} className={styles.item}> - <Teaser - {...item} - {...presets.SmallHeavy} - modifiers="timeline-teaser" - /> - {showSaveButtons && - <div className={classNames(styles.itemActions)}> - <ArticleSaveButton - id={`${item.id}-save-button`} - contentId={item.id} - contentTitle={item.title} - csrfToken={csrfToken} - saved={item.saved || false} - /> - </div>} - </li> - ); - } else if (typeof item === 'string') { - return (<li key="custom-slot" dangerouslySetInnerHTML={{ __html: item }} />); - } + return ( + itemGroups.length > 0 && ( + <div> + {itemGroups.map((group) => ( + <section key={group.date} className={classNames(styles.itemGroup)}> + <h2 className={classNames(styles.itemGroup__heading)}>{group.title}</h2> + <ul className={classNames(styles.itemGroup__items)}> + {group.items.map((item) => { + if (item.id) { + return ( + <li key={item.id} className={styles.item}> + <Teaser {...item} {...presets.SmallHeavy} modifiers="timeline-teaser" /> + {showSaveButtons && ( + <div className={classNames(styles.itemActions)}> + <ArticleSaveButton + id={`${item.id}-save-button`} + contentId={item.id} + contentTitle={item.title} + csrfToken={csrfToken} + saved={item.saved || false} + /> + </div> + )} + </li> + ) + } else if (typeof item === 'string') { + return <li key="custom-slot" dangerouslySetInnerHTML={{ __html: item }} /> + } - return (<li key="custom-slot">{item}</li>); - })} - </ul> - </section> - ))} - </div> - ); -}; + return <li key="custom-slot">{item}</li> + })} + </ul> + </section> + ))} + </div> + ) + ) +} -export { TeaserTimeline }; +export { TeaserTimeline } diff --git a/components/x-teaser-timeline/src/lib/date.js b/components/x-teaser-timeline/src/lib/date.js index 2ce0f5859..70bf95598 100644 --- a/components/x-teaser-timeline/src/lib/date.js +++ b/components/x-teaser-timeline/src/lib/date.js @@ -1,4 +1,4 @@ -import { differenceInCalendarDays, format, isAfter, subMinutes } from 'date-fns'; +import { differenceInCalendarDays, format, isAfter, subMinutes } from 'date-fns' /** * Takes a UTC ISO date/time and turns it into a ISO date for a particular timezone @@ -7,15 +7,15 @@ import { differenceInCalendarDays, format, isAfter, subMinutes } from 'date-fns' * @return {string} A localised ISO date, e.g. '2018-07-19T00:30:00.000+01:00' for UTC+1 */ export const getLocalisedISODate = (isoDate, timezoneOffset) => { - const dateWithoutTimezone = subMinutes(isoDate, timezoneOffset).toISOString().substring(0, 23); - const future = timezoneOffset <= 0; - const offsetMinutes = Math.abs(timezoneOffset); - const hours = Math.floor(offsetMinutes / 60); - const minutes = offsetMinutes % 60; - const pad = n => String(n).padStart(2, '0'); + const dateWithoutTimezone = subMinutes(isoDate, timezoneOffset).toISOString().substring(0, 23) + const future = timezoneOffset <= 0 + const offsetMinutes = Math.abs(timezoneOffset) + const hours = Math.floor(offsetMinutes / 60) + const minutes = offsetMinutes % 60 + const pad = (n) => String(n).padStart(2, '0') - return `${dateWithoutTimezone}${future ? '+' : '-'}${pad(hours)}:${pad(minutes)}`; -}; + return `${dateWithoutTimezone}${future ? '+' : '-'}${pad(hours)}:${pad(minutes)}` +} export const getTitleForItemGroup = (localDate, localTodayDate) => { if (localDate === 'today-latest') { @@ -23,29 +23,29 @@ export const getTitleForItemGroup = (localDate, localTodayDate) => { } if (localDate === 'today-earlier' || localDate === localTodayDate) { - return 'Earlier Today'; + return 'Earlier Today' } if (differenceInCalendarDays(localTodayDate, localDate) === 1) { - return 'Yesterday'; + return 'Yesterday' } - return format(localDate, 'MMMM D, YYYY'); -}; + return format(localDate, 'MMMM D, YYYY') +} export const splitLatestEarlier = (items, splitDate) => { - const latestItems = []; - const earlierItems = []; + const latestItems = [] + const earlierItems = [] - items.forEach(item => { + items.forEach((item) => { if (isAfter(item.localisedLastUpdated, splitDate)) { - latestItems.push(item); + latestItems.push(item) } else { - earlierItems.push(item); + earlierItems.push(item) } - }); + }) - return { latestItems, earlierItems }; -}; + return { latestItems, earlierItems } +} -export const getDateOnly = date => date.substr(0, 10); +export const getDateOnly = (date) => date.substr(0, 10) diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index 64ddbaed7..fab1c91dd 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -1,60 +1,61 @@ -import { - getLocalisedISODate, - getTitleForItemGroup, - getDateOnly -} from './date'; +import { getLocalisedISODate, getTitleForItemGroup, getDateOnly } from './date' -const groupItemsByLocalisedDate = (indexOffset, replaceLocalDate, localTodayDateTime, items, timezoneOffset) => { - const itemsByLocalisedDate = {}; +const groupItemsByLocalisedDate = ( + indexOffset, + replaceLocalDate, + localTodayDateTime, + items, + timezoneOffset +) => { + const itemsByLocalisedDate = {} items.forEach((item, index) => { - const localDateTime = getLocalisedISODate(item.publishedDate, timezoneOffset); - const localDate = getDateOnly(localDateTime); + const localDateTime = getLocalisedISODate(item.publishedDate, timezoneOffset) + const localDate = getDateOnly(localDateTime) if (!itemsByLocalisedDate.hasOwnProperty(localDate)) { - itemsByLocalisedDate[localDate] = []; + itemsByLocalisedDate[localDate] = [] } - item.localisedLastUpdated = localDateTime; - itemsByLocalisedDate[localDate].push({ articleIndex: indexOffset + index, ...item }); - }); + item.localisedLastUpdated = localDateTime + itemsByLocalisedDate[localDate].push({ articleIndex: indexOffset + index, ...item }) + }) - const localTodayDate = localTodayDateTime && getDateOnly(localTodayDateTime); + const localTodayDate = localTodayDateTime && getDateOnly(localTodayDateTime) return Object.entries(itemsByLocalisedDate).map(([localDate, items]) => ({ date: replaceLocalDate && localDate === localTodayDate ? 'today-earlier' : localDate, items - })); -}; + })) +} const splitLatestItems = (items, localTodayDate, latestItemsTime) => { - const latestNews = []; - const remainingItems = []; + const latestNews = [] + const remainingItems = [] - items.forEach( (item,index) => { + items.forEach((item, index) => { // These are ISO date strings so string comparison works when comparing them if they are in the same timezone - if( latestItemsTime && item.publishedDate > latestItemsTime ) { - latestNews.push({ articleIndex: index, ...item }); + if (latestItemsTime && item.publishedDate > latestItemsTime) { + latestNews.push({ articleIndex: index, ...item }) } else { - remainingItems.push(item); + remainingItems.push(item) } - }); + }) - return [latestNews,remainingItems]; -}; + return [latestNews, remainingItems] +} const addItemGroupTitles = (itemGroups, localTodayDate) => { - return itemGroups.map(group => { - group.title = getTitleForItemGroup(group.date, localTodayDate); + return itemGroups.map((group) => { + group.title = getTitleForItemGroup(group.date, localTodayDate) - return group; - }); -}; + return group + }) +} const isTimeWithinAgeRange = (time, localTodayDate, ageRangeHours) => - time && (new Date(localTodayDate) - new Date(time)) < (ageRangeHours * 60 * 60 * 1000); + time && new Date(localTodayDate) - new Date(time) < ageRangeHours * 60 * 60 * 1000 -const isTimeWithinToday = (time, localTodayDate) => - time && (getDateOnly(localTodayDate) === getDateOnly(time)); +const isTimeWithinToday = (time, localTodayDate) => time && getDateOnly(localTodayDate) === getDateOnly(time) /** * Determines whether a "Latest News" section can be shown in the timeline. @@ -69,9 +70,9 @@ const isTimeWithinToday = (time, localTodayDate) => * @returns {boolean} true if a "Latest News" section can be shown. */ const isLatestNewsSectionAllowed = (localTodayDate, latestItemsTime, latestItemsAgeRange) => - latestItemsAgeRange ? - isTimeWithinAgeRange(latestItemsTime, localTodayDate, latestItemsAgeRange) : - isTimeWithinToday(latestItemsTime, localTodayDate); + latestItemsAgeRange + ? isTimeWithinAgeRange(latestItemsTime, localTodayDate, latestItemsAgeRange) + : isTimeWithinToday(latestItemsTime, localTodayDate) /** * Groups items (articles) by date @@ -87,18 +88,36 @@ const isLatestNewsSectionAllowed = (localTodayDate, latestItemsTime, latestItems * @param {number} latestItemsAgeRange Maximum age allowed for items in "Latest News". Hours. * @returns An array of group objects, each containing the group's title, date and items. */ -const getItemGroups = ({items, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours: latestItemsAgeRange}) => { +const getItemGroups = ({ + items, + timezoneOffset, + localTodayDate, + latestItemsTime, + latestItemsAgeHours: latestItemsAgeRange +}) => { if (!items || !Array.isArray(items) || items.length === 0) { - return []; + return [] } - const sortedItems = [...items].sort( (a,b) => a.publishedDate > b.publishedDate ? -1 : 1 ); + const sortedItems = [...items].sort((a, b) => (a.publishedDate > b.publishedDate ? -1 : 1)) - const includeLatesNewsSection = isLatestNewsSectionAllowed(localTodayDate, latestItemsTime, latestItemsAgeRange); + const includeLatesNewsSection = isLatestNewsSectionAllowed( + localTodayDate, + latestItemsTime, + latestItemsAgeRange + ) - const [latestItems,remainingItems] = includeLatesNewsSection ? splitLatestItems(sortedItems, localTodayDate, latestItemsTime) : [[],sortedItems]; + const [latestItems, remainingItems] = includeLatesNewsSection + ? splitLatestItems(sortedItems, localTodayDate, latestItemsTime) + : [[], sortedItems] - let itemGroups = groupItemsByLocalisedDate(latestItems.length, includeLatesNewsSection, localTodayDate, remainingItems, timezoneOffset); + let itemGroups = groupItemsByLocalisedDate( + latestItems.length, + includeLatesNewsSection, + localTodayDate, + remainingItems, + timezoneOffset + ) if (latestItems.length > 0) { itemGroups = [ @@ -107,40 +126,54 @@ const getItemGroups = ({items, timezoneOffset, localTodayDate, latestItemsTime, items: latestItems }, ...itemGroups - ]; + ] } - return addItemGroupTitles(itemGroups, localTodayDate); -}; + return addItemGroupTitles(itemGroups, localTodayDate) +} const getGroupAndIndex = (groups, position) => { if (position > 0) { - const group = groups.findIndex(g => g.items.some(item => item.articleIndex === position - 1)); - const index = groups[group].items.findIndex(item => item.articleIndex === position - 1); + const group = groups.findIndex((g) => g.items.some((item) => item.articleIndex === position - 1)) + const index = groups[group].items.findIndex((item) => item.articleIndex === position - 1) return { group: group, index: index + 1 - }; + } } return { group: 0, index: 0 - }; -}; - -export const buildModel = ({items, customSlotContent, customSlotPosition, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}) => { - const itemGroups = getItemGroups({items, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours}); + } +} + +export const buildModel = ({ + items, + customSlotContent, + customSlotPosition, + timezoneOffset, + localTodayDate, + latestItemsTime, + latestItemsAgeHours +}) => { + const itemGroups = getItemGroups({ + items, + timezoneOffset, + localTodayDate, + latestItemsTime, + latestItemsAgeHours + }) if (itemGroups.length > 0 && customSlotContent) { - const insertPosition = Math.min(customSlotPosition, items.length); - const insert = getGroupAndIndex(itemGroups, insertPosition); - const copyOfItems = [...itemGroups[insert.group].items]; + const insertPosition = Math.min(customSlotPosition, items.length) + const insert = getGroupAndIndex(itemGroups, insertPosition) + const copyOfItems = [...itemGroups[insert.group].items] - copyOfItems.splice(insert.index, 0, customSlotContent); + copyOfItems.splice(insert.index, 0, customSlotContent) - itemGroups[insert.group].items = copyOfItems; + itemGroups[insert.group].items = copyOfItems } - return itemGroups; -}; + return itemGroups +} diff --git a/components/x-teaser-timeline/stories/index.js b/components/x-teaser-timeline/stories/index.js index c2a2753b5..308fd5b86 100644 --- a/components/x-teaser-timeline/stories/index.js +++ b/components/x-teaser-timeline/stories/index.js @@ -1,18 +1,16 @@ -const { TeaserTimeline } = require('../'); +const { TeaserTimeline } = require('../') -exports.component = TeaserTimeline; +exports.component = TeaserTimeline -exports.package = require('../package.json'); +exports.package = require('../package.json') // Set up basic document styling using the Origami build service exports.dependencies = { 'o-normalise': '^1.6.0', 'o-typography': '^5.5.0', 'o-teaser': '^2.3.1' -}; +} -exports.stories = [ - require('./timeline'), -]; +exports.stories = [require('./timeline')] -exports.knobs = require('./knobs'); +exports.knobs = require('./knobs') diff --git a/components/x-teaser-timeline/stories/knobs.js b/components/x-teaser-timeline/stories/knobs.js index c4d730842..56cdc5055 100644 --- a/components/x-teaser-timeline/stories/knobs.js +++ b/components/x-teaser-timeline/stories/knobs.js @@ -1,24 +1,19 @@ module.exports = (data, { number, select }) => { return { latestItemsTime() { - return select( - 'Latest Items Time', - { - None: '', - '2018-10-17T12:10:33.000Z': '2018-10-17T12:10:33.000Z' - }); + return select('Latest Items Time', { + None: '', + '2018-10-17T12:10:33.000Z': '2018-10-17T12:10:33.000Z' + }) }, customSlotPosition() { - return number('Custom Slot Position', data.customSlotPosition); + return number('Custom Slot Position', data.customSlotPosition) }, customSlotContent() { - return select( - 'Custom Slot Content', - { - None: '', - Something: '---Custom slot content---' - }); + return select('Custom Slot Content', { + None: '', + Something: '---Custom slot content---' + }) } - }; -}; - + } +} diff --git a/components/x-teaser-timeline/stories/timeline.js b/components/x-teaser-timeline/stories/timeline.js index da93a2ad1..7a200bbc6 100644 --- a/components/x-teaser-timeline/stories/timeline.js +++ b/components/x-teaser-timeline/stories/timeline.js @@ -1,4 +1,4 @@ -exports.title = 'Timeline'; +exports.title = 'Timeline' exports.data = { items: require('./content-items.json'), @@ -7,10 +7,10 @@ exports.data = { latestItemsTime: '2018-10-17T12:10:33.000Z', customSlotContent: 'Custom slot content', customSlotPosition: 3 -}; +} -exports.knobs = Object.keys(exports.data); +exports.knobs = Object.keys(exports.data) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser/Props.d.ts b/components/x-teaser/Props.d.ts index 3b12a7852..6d1e9de91 100644 --- a/components/x-teaser/Props.d.ts +++ b/components/x-teaser/Props.d.ts @@ -1,106 +1,121 @@ -export type ContentType = 'article' | 'video' | 'podcast' | 'audio' | 'package' | 'liveblog' | 'promoted-content' | 'paid-post'; +export type ContentType = + | 'article' + | 'video' + | 'podcast' + | 'audio' + | 'package' + | 'liveblog' + | 'promoted-content' + | 'paid-post' /** Strings must be a parseable format, e.g. ISO 8601 */ -export type DateLike = Date | string | number; +export type DateLike = Date | string | number -export type Layout = 'small' | 'large' | 'hero' | 'top-story'; +export type Layout = 'small' | 'large' | 'hero' | 'top-story' -export type Theme = 'extra-article'; +export type Theme = 'extra-article' -export type Modifier = 'stacked' | 'centre' | 'stretched' | 'opinion-background' | 'landscape' | 'big-story' | string; +export type Modifier = + | 'stacked' + | 'centre' + | 'stretched' + | 'opinion-background' + | 'landscape' + | 'big-story' + | string -export type ImageSize = 'XS' | 'Small' | 'Medium' | 'Large' | 'XL' | 'XXL'; +export type ImageSize = 'XS' | 'Small' | 'Medium' | 'Large' | 'XL' | 'XXL' export interface Features { - showMeta?: boolean; - showTitle?: boolean; - showStandfirst?: boolean; - showStatus?: boolean; - showImage?: boolean; - showHeadshot?: boolean; - showVideo?: boolean; - showRelatedLinks?: boolean; - showCustomSlot?: boolean; + showMeta?: boolean + showTitle?: boolean + showStandfirst?: boolean + showStatus?: boolean + showImage?: boolean + showHeadshot?: boolean + showVideo?: boolean + showRelatedLinks?: boolean + showCustomSlot?: boolean } export interface General { - id: string; - url?: string; - /** Preferred to url if available */ - relativeUrl?: string; - type: ContentType; - indicators: Indicators; + id: string + url?: string + /** Preferred to url if available */ + relativeUrl?: string + type: ContentType + indicators: Indicators } export interface Meta { - /** Usually a brand, or a genre, or content type */ - metaPrefixText?: string; - metaSuffixText?: string; - metaLink?: MetaLink; - /** Fallback used if the parentId is the same as the display concept */ - metaAltLink?: MetaLink; - /** Promoted content type */ - promotedPrefixText?: string; - promotedSuffixText?: string; + /** Usually a brand, or a genre, or content type */ + metaPrefixText?: string + metaSuffixText?: string + metaLink?: MetaLink + /** Fallback used if the parentId is the same as the display concept */ + metaAltLink?: MetaLink + /** Promoted content type */ + promotedPrefixText?: string + promotedSuffixText?: string } export interface Title { - title: string; - /** Used for testing headline variations */ - altTitle?: string; + title: string + /** Used for testing headline variations */ + altTitle?: string } export interface Standfirst { - standfirst?: string; - /** Used for testing standfirst variations */ - altStandfirst?: string; + standfirst?: string + /** Used for testing standfirst variations */ + altStandfirst?: string } export interface Status { - publishedDate: DateLike; - firstPublishedDate: DateLike; - /** Displays new/updated X mins/hours ago */ - useRelativeTime?: boolean; - /** Live blog status, will override date and time */ - status?: 'inprogress' | 'comingsoon' | 'closed'; + publishedDate: DateLike + firstPublishedDate: DateLike + /** Displays new/updated X mins/hours ago */ + useRelativeTime?: boolean + /** Live blog status, will override date and time */ + status?: 'inprogress' | 'comingsoon' | 'closed' } export interface Image { - /** Images must be accessible to the Origami Image Service */ - image?: Media; - imageSize?: ImageSize; - imageLazyLoad?: Boolean | String; + /** Images must be accessible to the Origami Image Service */ + image?: Media + imageSize?: ImageSize + imageLazyLoad?: Boolean | String } export interface Headshot { - headshot?: String; - headshotTint?: String; + headshot?: String + headshotTint?: String } export interface Video { - video?: Media + video?: Media } export interface RelatedLinks { - relatedLinks?: Link[]; + relatedLinks?: Link[] } export interface Context { - /** Enables alternative content for headline testing */ - headlineTesting?: Boolean; - /** Shows the alternative meta link when the label matches */ - parentLabel?: String; - /** Shows the alternative meta link when the ID matches */ - parentId?: String; + /** Enables alternative content for headline testing */ + headlineTesting?: Boolean + /** Shows the alternative meta link when the label matches */ + parentLabel?: String + /** Shows the alternative meta link when the ID matches */ + parentId?: String } export interface Variants { - /** Default is "small" */ - layout?: Layout; - /** Content package theme */ - theme?: Theme; - /** Extra class name variations to append */ - modifiers?: Modifier[]; + /** Default is "small" */ + layout?: Layout + /** Content package theme */ + theme?: Theme + /** Extra class name variations to append */ + modifiers?: Modifier[] } // @@ -108,36 +123,48 @@ export interface Variants { // export interface MetaLink { - url: string; - /** Preferred if available */ - relativeUrl?; - prefLabel: string; + url: string + /** Preferred if available */ + relativeUrl? + prefLabel: string } export interface Link { - id: string; - type: ContentType; - url: string; - /** Preferred to url if available */ - relativeUrl?; - title: string; + id: string + type: ContentType + url: string + /** Preferred to url if available */ + relativeUrl? + title: string } export interface Media { - url: string; - width: number; - height: number; + url: string + width: number + height: number } export interface Indicators { - accessLevel: 'premium' | 'subscribed' | 'registered' | 'free'; - isOpinion?: boolean; - isColumn?: boolean; - isPodcast?: boolean; - /** Methode packaging options */ - isEditorsChoice?: boolean; - isExclusive?: boolean; - isScoop?: boolean; -} - -export interface TeaserProps extends Features, General, Meta, Title, Standfirst, Status, Image, Headshot, Video, RelatedLinks, Context, Variants {} + accessLevel: 'premium' | 'subscribed' | 'registered' | 'free' + isOpinion?: boolean + isColumn?: boolean + isPodcast?: boolean + /** Methode packaging options */ + isEditorsChoice?: boolean + isExclusive?: boolean + isScoop?: boolean +} + +export interface TeaserProps + extends Features, + General, + Meta, + Title, + Standfirst, + Status, + Image, + Headshot, + Video, + RelatedLinks, + Context, + Variants {} diff --git a/components/x-teaser/__tests__/snapshots.test.js b/components/x-teaser/__tests__/snapshots.test.js index 5f95c7400..182ea7993 100644 --- a/components/x-teaser/__tests__/snapshots.test.js +++ b/components/x-teaser/__tests__/snapshots.test.js @@ -1,6 +1,6 @@ -const { h } = require('@financial-times/x-engine'); -const renderer = require('react-test-renderer'); -const { Teaser, presets } = require('../'); +const { h } = require('@financial-times/x-engine') +const renderer = require('react-test-renderer') +const { Teaser, presets } = require('../') const storyData = { article: require('../__fixtures__/article.json'), @@ -12,17 +12,17 @@ const storyData = { video: require('../__fixtures__/video.json'), promoted: require('../__fixtures__/promoted.json'), topStory: require('../__fixtures__/top-story.json') -}; +} describe('x-teaser / snapshots', () => { Object.entries(storyData).forEach(([type, data]) => { Object.entries(presets).forEach(([name, settings]) => { it(`renders a ${name} teaser with ${type} data`, () => { - const props = { ...data, ...settings }; - const tree = renderer.create(h(Teaser, props)).toJSON(); + const props = { ...data, ...settings } + const tree = renderer.create(h(Teaser, props)).toJSON() - expect(tree).toMatchSnapshot(); - }); - }); - }); -}); + expect(tree).toMatchSnapshot() + }) + }) + }) +}) diff --git a/components/x-teaser/rollup.js b/components/x-teaser/rollup.js index 6bd63c6aa..defac3883 100644 --- a/components/x-teaser/rollup.js +++ b/components/x-teaser/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/Teaser.jsx', pkg }); +xRollup({ input: './src/Teaser.jsx', pkg }) diff --git a/components/x-teaser/src/Container.jsx b/components/x-teaser/src/Container.jsx index 4c6a93d89..8a4e66c5e 100644 --- a/components/x-teaser/src/Container.jsx +++ b/components/x-teaser/src/Container.jsx @@ -1,37 +1,40 @@ -import { h } from '@financial-times/x-engine'; -import { media, theme } from './concerns/rules'; +import { h } from '@financial-times/x-engine' +import { media, theme } from './concerns/rules' const dynamicModifiers = (props) => { - const modifiers = []; + const modifiers = [] - const mediaRule = media(props); + const mediaRule = media(props) if (mediaRule) { - modifiers.push(`has-${mediaRule}`); + modifiers.push(`has-${mediaRule}`) } - const themeRule = theme(props); + const themeRule = theme(props) if (themeRule) { - modifiers.push(themeRule); + modifiers.push(themeRule) } - return modifiers; -}; + return modifiers +} export default (props) => { - const computed = dynamicModifiers(props); + const computed = dynamicModifiers(props) // Modifier props may be a string rather than a string[] so concat, don't spread. - const variants = [props.type, props.layout].concat(props.modifiers, computed); + const variants = [props.type, props.layout].concat(props.modifiers, computed) const classNames = variants .filter(Boolean) .map((mod) => `o-teaser--${mod}`) - .join(' '); + .join(' ') return ( - <div className={`o-teaser ${classNames} js-teaser`} data-id={props.id} data-trackable={props.dataTrackable}> + <div + className={`o-teaser ${classNames} js-teaser`} + data-id={props.id} + data-trackable={props.dataTrackable}> {props.children} </div> - ); -}; + ) +} diff --git a/components/x-teaser/src/Content.jsx b/components/x-teaser/src/Content.jsx index 0e40b54f8..aa573c29c 100644 --- a/components/x-teaser/src/Content.jsx +++ b/components/x-teaser/src/Content.jsx @@ -1,5 +1,3 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' -export default ({ children = [] }) => ( - <div className="o-teaser__content">{children}</div> -); +export default ({ children = [] }) => <div className="o-teaser__content">{children}</div> diff --git a/components/x-teaser/src/CustomSlot.jsx b/components/x-teaser/src/CustomSlot.jsx index 02cfc9900..dc898cfc7 100644 --- a/components/x-teaser/src/CustomSlot.jsx +++ b/components/x-teaser/src/CustomSlot.jsx @@ -1,4 +1,4 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' /** * Render @@ -8,12 +8,11 @@ import { h } from '@financial-times/x-engine'; const render = (action) => { // Allow parent components to pass raw HTML strings if (typeof action === 'string') { - return <span dangerouslySetInnerHTML={{ __html: action }} />; + return <span dangerouslySetInnerHTML={{ __html: action }} /> } else { - return action; + return action } -}; +} -export default ({ customSlot }) => ( +export default ({ customSlot }) => customSlot ? <div className="o-teaser__action">{render(customSlot)}</div> : null -); diff --git a/components/x-teaser/src/Headshot.jsx b/components/x-teaser/src/Headshot.jsx index cbe832468..fc5b204ab 100644 --- a/components/x-teaser/src/Headshot.jsx +++ b/components/x-teaser/src/Headshot.jsx @@ -1,12 +1,12 @@ -import { h } from '@financial-times/x-engine'; -import { ImageSizes } from './concerns/constants'; -import imageService from './concerns/image-service'; +import { h } from '@financial-times/x-engine' +import { ImageSizes } from './concerns/constants' +import imageService from './concerns/image-service' // these colours are tweaked from o-colors palette colours to make headshots look less washed out -const DEFAULT_TINT = '054593,d6d5d3'; +const DEFAULT_TINT = '054593,d6d5d3' export default ({ headshot, headshotTint }) => { - const options = `tint=${headshotTint || DEFAULT_TINT}`; + const options = `tint=${headshotTint || DEFAULT_TINT}` return headshot ? ( <img @@ -17,5 +17,5 @@ export default ({ headshot, headshotTint }) => { aria-hidden="true" src={imageService(headshot, ImageSizes.Headshot, options)} /> - ) : null; -}; + ) : null +} diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 3c02bacc8..79e0fa704 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -1,7 +1,7 @@ -import { h } from '@financial-times/x-engine'; -import { ImageSizes } from './concerns/constants'; -import imageService from './concerns/image-service'; -import Link from './Link'; +import { h } from '@financial-times/x-engine' +import { ImageSizes } from './concerns/constants' +import imageService from './concerns/image-service' +import Link from './Link' /** * Aspect Ratio @@ -10,40 +10,41 @@ import Link from './Link'; */ const aspectRatio = ({ width, height }) => { if (typeof width === 'number' && typeof height === 'number') { - const ratio = (100 / width) * height; - return ratio.toFixed(4) + '%'; + const ratio = (100 / width) * height + return ratio.toFixed(4) + '%' } - return null; -}; + return null +} -const NormalImage = ({ src }) => ( - <img className="o-teaser__image" src={src} alt="" /> -); +const NormalImage = ({ src }) => <img className="o-teaser__image" src={src} alt="" /> const LazyImage = ({ src, lazyLoad }) => { - const lazyClassName = typeof lazyLoad === 'string' ? lazyLoad : ''; - return <img className={`o-teaser__image ${lazyClassName}`} data-src={src} alt="" />; -}; + const lazyClassName = typeof lazyLoad === 'string' ? lazyLoad : '' + return <img className={`o-teaser__image ${lazyClassName}`} data-src={src} alt="" /> +} export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, imageHighestQuality, ...props }) => { - const displayUrl = relativeUrl || url; - const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')); - const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {}; - const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize], options) : image.url; - const ImageComponent = imageLazyLoad ? LazyImage : NormalImage; + const displayUrl = relativeUrl || url + const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')) + const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {} + const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize], options) : image.url + const ImageComponent = imageLazyLoad ? LazyImage : NormalImage return image ? ( <div className="o-teaser__image-container js-teaser-image-container"> - <Link {...props} url={displayUrl} attrs={{ + <Link + {...props} + url={displayUrl} + attrs={{ 'data-trackable': 'image-link', - 'tabIndex': '-1', - 'aria-hidden': 'true', + tabIndex: '-1', + 'aria-hidden': 'true' }}> <div className="o-teaser__image-placeholder" style={{ paddingBottom: aspectRatio(image) }}> <ImageComponent src={imageSrc} lazyLoad={imageLazyLoad} /> </div> </Link> </div> - ) : null; -}; + ) : null +} diff --git a/components/x-teaser/src/Link.jsx b/components/x-teaser/src/Link.jsx index 9f4baa741..049797ed3 100644 --- a/components/x-teaser/src/Link.jsx +++ b/components/x-teaser/src/Link.jsx @@ -1,14 +1,18 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' const BaseLink = ({ url, attrs = {}, children }) => { if (url) { - return <a href={url} {...attrs}>{children}</a>; + return ( + <a href={url} {...attrs}> + {children} + </a> + ) } else { - return <span {...attrs}>{children}</span>; + return <span {...attrs}>{children}</span> } -}; +} export default ({ customElements = {}, ...props }) => { - const Link = customElements.Link || BaseLink; - return <Link {...props} />; -}; + const Link = customElements.Link || BaseLink + return <Link {...props} /> +} diff --git a/components/x-teaser/src/LiveBlogStatus.jsx b/components/x-teaser/src/LiveBlogStatus.jsx index 30df7854c..c6447eb28 100644 --- a/components/x-teaser/src/LiveBlogStatus.jsx +++ b/components/x-teaser/src/LiveBlogStatus.jsx @@ -1,21 +1,20 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' const LiveBlogLabels = { inprogress: 'Live', comingsoon: 'Coming Soon', closed: '' -}; +} const LiveBlogModifiers = { inprogress: 'live', comingsoon: 'pending', closed: 'closed' -}; +} -export default ({ status }) => ( +export default ({ status }) => status && status !== 'closed' ? ( <div className={`o-teaser__timestamp o-teaser__timestamp--${LiveBlogModifiers[status]}`}> <span className="o-teaser__timestamp-prefix">{LiveBlogLabels[status]}</span> </div> ) : null -); diff --git a/components/x-teaser/src/Meta.jsx b/components/x-teaser/src/Meta.jsx index 9251b6ef3..2977c4099 100644 --- a/components/x-teaser/src/Meta.jsx +++ b/components/x-teaser/src/Meta.jsx @@ -1,9 +1,9 @@ -import { h } from '@financial-times/x-engine'; -import MetaLink from './MetaLink'; -import Promoted from './Promoted'; +import { h } from '@financial-times/x-engine' +import MetaLink from './MetaLink' +import Promoted from './Promoted' export default (props) => { - const showPromoted = props.promotedPrefixText && props.promotedSuffixText; + const showPromoted = props.promotedPrefixText && props.promotedSuffixText - return showPromoted ? <Promoted {...props} /> : <MetaLink {...props} />; -}; + return showPromoted ? <Promoted {...props} /> : <MetaLink {...props} /> +} diff --git a/components/x-teaser/src/MetaLink.jsx b/components/x-teaser/src/MetaLink.jsx index d1ebe68bd..93def3b90 100644 --- a/components/x-teaser/src/MetaLink.jsx +++ b/components/x-teaser/src/MetaLink.jsx @@ -1,20 +1,20 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' const sameId = (context = {}, id) => { - return id && context && context.parentId && id === context.parentId; -}; + return id && context && context.parentId && id === context.parentId +} const sameLabel = (context = {}, label) => { - return label && context && context.parentLabel && label === context.parentLabel; -}; + return label && context && context.parentLabel && label === context.parentLabel +} export default ({ metaPrefixText, metaLink, metaAltLink, metaSuffixText, context }) => { - const showPrefixText = metaPrefixText && !sameLabel(context, metaPrefixText); - const showSuffixText = metaSuffixText && !sameLabel(context, metaSuffixText); - const linkId = metaLink && metaLink.id; - const linkLabel = metaLink && metaLink.prefLabel; - const useAltLink = sameId(context, linkId) || sameLabel(context, linkLabel); - const displayLink = useAltLink ? metaAltLink : metaLink; + const showPrefixText = metaPrefixText && !sameLabel(context, metaPrefixText) + const showSuffixText = metaSuffixText && !sameLabel(context, metaSuffixText) + const linkId = metaLink && metaLink.id + const linkLabel = metaLink && metaLink.prefLabel + const useAltLink = sameId(context, linkId) || sameLabel(context, linkLabel) + const displayLink = useAltLink ? metaAltLink : metaLink return ( <div className="o-teaser__meta"> @@ -30,5 +30,5 @@ export default ({ metaPrefixText, metaLink, metaAltLink, metaSuffixText, context ) : null} {showSuffixText ? <span className="o-teaser__tag-suffix">{metaSuffixText}</span> : null} </div> - ); -}; + ) +} diff --git a/components/x-teaser/src/Promoted.jsx b/components/x-teaser/src/Promoted.jsx index 8adaa09c3..79a1378c6 100644 --- a/components/x-teaser/src/Promoted.jsx +++ b/components/x-teaser/src/Promoted.jsx @@ -1,8 +1,8 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' export default ({ promotedPrefixText, promotedSuffixText }) => ( <div className="o-teaser__meta"> <span className="o-teaser__promoted-prefix">{promotedPrefixText}</span> by <span className="o-teaser__promoted-by">{` ${promotedSuffixText} `}</span> </div> -); +) diff --git a/components/x-teaser/src/RelatedLinks.jsx b/components/x-teaser/src/RelatedLinks.jsx index 34ecff94d..4c954b387 100644 --- a/components/x-teaser/src/RelatedLinks.jsx +++ b/components/x-teaser/src/RelatedLinks.jsx @@ -1,7 +1,7 @@ -import { h } from '@financial-times/x-engine'; +import { h } from '@financial-times/x-engine' const renderLink = ({ id, type, title, url, relativeUrl }, i) => { - const displayUrl = relativeUrl || url; + const displayUrl = relativeUrl || url return ( <li key={`related-${i}`} @@ -11,11 +11,10 @@ const renderLink = ({ id, type, title, url, relativeUrl }, i) => { {title} </a> </li> - ); + ) } -export default ({ relatedLinks = [] }) => ( +export default ({ relatedLinks = [] }) => relatedLinks && relatedLinks.length ? ( <ul className="o-teaser__related">{relatedLinks.map(renderLink)}</ul> ) : null -); diff --git a/components/x-teaser/src/RelativeTime.jsx b/components/x-teaser/src/RelativeTime.jsx index 60f868d6a..176c056c5 100644 --- a/components/x-teaser/src/RelativeTime.jsx +++ b/components/x-teaser/src/RelativeTime.jsx @@ -1,6 +1,6 @@ -import { h } from '@financial-times/x-engine'; -import { isRecent, getRelativeDate, getStatus } from './concerns/date-time'; -import dateformat from 'dateformat'; +import { h } from '@financial-times/x-engine' +import { isRecent, getRelativeDate, getStatus } from './concerns/date-time' +import dateformat from 'dateformat' /** * Display Time @@ -8,16 +8,16 @@ import dateformat from 'dateformat'; * @returns {String} */ const displayTime = (date) => { - const hours = Math.floor(Math.abs(date / 3600000)); - const plural = hours === 1 ? 'hour' : 'hours'; - const suffix = hours === 0 ? '' : `${plural} ago`; + const hours = Math.floor(Math.abs(date / 3600000)) + const plural = hours === 1 ? 'hour' : 'hours' + const suffix = hours === 0 ? '' : `${plural} ago` - return `${hours} ${suffix}`; -}; + return `${hours} ${suffix}` +} export default ({ publishedDate, firstPublishedDate }) => { - const relativeDate = getRelativeDate(publishedDate); - const status = getStatus(publishedDate, firstPublishedDate); + const relativeDate = getRelativeDate(publishedDate) + const status = getStatus(publishedDate, firstPublishedDate) return isRecent(relativeDate) ? ( <div className={`o-teaser__timestamp o-teaser__timestamp--${status}`}> @@ -31,5 +31,5 @@ export default ({ publishedDate, firstPublishedDate }) => { {status ? '' : displayTime(relativeDate)} </time> </div> - ) : null; -}; + ) : null +} diff --git a/components/x-teaser/src/Standfirst.jsx b/components/x-teaser/src/Standfirst.jsx index 1bb1b42de..b03880983 100644 --- a/components/x-teaser/src/Standfirst.jsx +++ b/components/x-teaser/src/Standfirst.jsx @@ -1,19 +1,21 @@ -import { h } from '@financial-times/x-engine'; -import Link from './Link'; - +import { h } from '@financial-times/x-engine' +import Link from './Link' export default ({ standfirst, altStandfirst, headlineTesting, relativeUrl, url, ...props }) => { - const displayStandfirst = headlineTesting && altStandfirst ? altStandfirst : standfirst; - const displayUrl = relativeUrl || url; - return displayStandfirst ? - <p className="o-teaser__standfirst"> - <Link {...props} url={displayUrl} attrs={{ - 'data-trackable': 'standfirst-link', - tabIndex:-1, - className: 'js-teaser-standfirst-link', - }}> - {displayStandfirst} - </Link> - </p> - : null; -}; + const displayStandfirst = headlineTesting && altStandfirst ? altStandfirst : standfirst + const displayUrl = relativeUrl || url + return displayStandfirst ? ( + <p className="o-teaser__standfirst"> + <Link + {...props} + url={displayUrl} + attrs={{ + 'data-trackable': 'standfirst-link', + tabIndex: -1, + className: 'js-teaser-standfirst-link' + }}> + {displayStandfirst} + </Link> + </p> + ) : null +} diff --git a/components/x-teaser/src/Status.jsx b/components/x-teaser/src/Status.jsx index 2b2275466..97bd8a5cd 100644 --- a/components/x-teaser/src/Status.jsx +++ b/components/x-teaser/src/Status.jsx @@ -1,20 +1,20 @@ -import { h } from '@financial-times/x-engine'; -import TimeStamp from './TimeStamp'; -import RelativeTime from './RelativeTime'; -import LiveBlogStatus from './LiveBlogStatus'; +import { h } from '@financial-times/x-engine' +import TimeStamp from './TimeStamp' +import RelativeTime from './RelativeTime' +import LiveBlogStatus from './LiveBlogStatus' export default (props) => { if (props.status) { - return <LiveBlogStatus {...props} />; + return <LiveBlogStatus {...props} /> } if (props.publishedDate) { if (props.useRelativeTime) { - return <RelativeTime {...props} />; + return <RelativeTime {...props} /> } else { - return <TimeStamp {...props} />; + return <TimeStamp {...props} /> } } - return null; -}; + return null +} diff --git a/components/x-teaser/src/Teaser.jsx b/components/x-teaser/src/Teaser.jsx index 15076487d..7d3a9841e 100644 --- a/components/x-teaser/src/Teaser.jsx +++ b/components/x-teaser/src/Teaser.jsx @@ -1,17 +1,17 @@ -import { h } from '@financial-times/x-engine'; -import Container from './Container'; -import Content from './Content'; -import CustomSlot from './CustomSlot'; -import Headshot from './Headshot'; -import Image from './Image'; -import Meta from './Meta'; -import RelatedLinks from './RelatedLinks'; -import Status from './Status'; -import Standfirst from './Standfirst'; -import Title from './Title'; -import Video from './Video'; -import { media } from './concerns/rules'; -import presets from './concerns/presets'; +import { h } from '@financial-times/x-engine' +import Container from './Container' +import Content from './Content' +import CustomSlot from './CustomSlot' +import Headshot from './Headshot' +import Image from './Image' +import Meta from './Meta' +import RelatedLinks from './RelatedLinks' +import Status from './Status' +import Standfirst from './Standfirst' +import Title from './Title' +import Video from './Video' +import { media } from './concerns/rules' +import presets from './concerns/presets' const Teaser = (props) => ( <Container {...props}> @@ -27,7 +27,7 @@ const Teaser = (props) => ( {media(props) === 'image' ? <Image {...props} /> : null} {props.showRelatedLinks ? <RelatedLinks {...props} /> : null} </Container> -); +) export { Container, @@ -43,4 +43,4 @@ export { Title, Video, presets -}; +} diff --git a/components/x-teaser/src/TimeStamp.jsx b/components/x-teaser/src/TimeStamp.jsx index 4a2677091..a10535f4d 100644 --- a/components/x-teaser/src/TimeStamp.jsx +++ b/components/x-teaser/src/TimeStamp.jsx @@ -1,5 +1,5 @@ -import { h } from '@financial-times/x-engine'; -import dateformat from 'dateformat'; +import { h } from '@financial-times/x-engine' +import dateformat from 'dateformat' export default ({ publishedDate }) => ( <div className="o-teaser__timestamp"> @@ -9,4 +9,4 @@ export default ({ publishedDate }) => ( {dateformat(publishedDate, dateformat.masks.longDate, true)} </time> </div> -); +) diff --git a/components/x-teaser/src/Title.jsx b/components/x-teaser/src/Title.jsx index e693e1cf3..42d8f73d5 100644 --- a/components/x-teaser/src/Title.jsx +++ b/components/x-teaser/src/Title.jsx @@ -1,13 +1,13 @@ -import { h } from '@financial-times/x-engine'; -import Link from './Link'; +import { h } from '@financial-times/x-engine' +import Link from './Link' export default ({ title, altTitle, headlineTesting, relativeUrl, url, indicators, ...props }) => { - const displayTitle = headlineTesting && altTitle ? altTitle : title; - const displayUrl = relativeUrl || url; + const displayTitle = headlineTesting && altTitle ? altTitle : title + const displayUrl = relativeUrl || url // o-labels--premium left for backwards compatibility for o-labels v3 - const premiumClass = 'o-labels o-labels--premium o-labels--content-premium'; - let ariaLabel; - if(props.type === 'video') { + const premiumClass = 'o-labels o-labels--premium o-labels--content-premium' + let ariaLabel + if (props.type === 'video') { ariaLabel = `Watch video ${displayTitle}` } else if (props.type === 'audio') { ariaLabel = `Listen to podcast ${displayTitle}` @@ -15,11 +15,14 @@ export default ({ title, altTitle, headlineTesting, relativeUrl, url, indicators return ( <div className="o-teaser__heading"> - <Link {...props} url={displayUrl} attrs={{ - 'data-trackable': 'heading-link', - className: 'js-teaser-heading-link', - 'aria-label': ariaLabel - }}> + <Link + {...props} + url={displayUrl} + attrs={{ + 'data-trackable': 'heading-link', + className: 'js-teaser-heading-link', + 'aria-label': ariaLabel + }}> {displayTitle} </Link> {indicators && indicators.accessLevel === 'premium' ? ( @@ -31,5 +34,5 @@ export default ({ title, altTitle, headlineTesting, relativeUrl, url, indicators </span> ) : null} </div> - ); -}; + ) +} diff --git a/components/x-teaser/src/Video.jsx b/components/x-teaser/src/Video.jsx index 9d35ad912..eda359113 100644 --- a/components/x-teaser/src/Video.jsx +++ b/components/x-teaser/src/Video.jsx @@ -1,35 +1,37 @@ -import { h } from '@financial-times/x-engine'; -import Image from './Image'; +import { h } from '@financial-times/x-engine' +import Image from './Image' // Re-format the data for use with o-video -const formatData = (props) => JSON.stringify({ - renditions: [ props.video ], - mainImageUrl: props.image ? props.image.url : null, -}); +const formatData = (props) => + JSON.stringify({ + renditions: [props.video], + mainImageUrl: props.image ? props.image.url : null + }) // To prevent React from touching the DOM after mounting… return an empty <div /> // <https://reactjs.org/docs/integrating-with-other-libraries.html> const Embed = (props) => { - const showGuidance = typeof props.showGuidance === 'boolean' ? props.showGuidance.toString() : "true"; + const showGuidance = typeof props.showGuidance === 'boolean' ? props.showGuidance.toString() : 'true' return ( - <div className="o-teaser__image-container js-image-container"> - <div - className="o-video" - data-o-component="o-video" - data-o-video-id={props.id} - data-o-video-data={formatData(props)} - data-o-video-optimumwidth="640" - data-o-video-optimumvideowidth="640" - data-o-video-autorender="true" - data-o-video-playsinline="true" - data-o-video-show-guidance={showGuidance} - data-o-video-placeholder="true" - data-o-video-placeholder-info="[]" - data-o-video-placeholder-hint="Play video" - data-o-video-systemcode={props.systemCode} /> - </div> + <div className="o-teaser__image-container js-image-container"> + <div + className="o-video" + data-o-component="o-video" + data-o-video-id={props.id} + data-o-video-data={formatData(props)} + data-o-video-optimumwidth="640" + data-o-video-optimumvideowidth="640" + data-o-video-autorender="true" + data-o-video-playsinline="true" + data-o-video-show-guidance={showGuidance} + data-o-video-placeholder="true" + data-o-video-placeholder-info="[]" + data-o-video-placeholder-hint="Play video" + data-o-video-systemcode={props.systemCode} + /> + </div> ) -}; +} export default (props) => ( <div className="o-teaser__video"> @@ -40,4 +42,4 @@ export default (props) => ( <Image {...props} /> </div> </div> -); +) diff --git a/components/x-teaser/src/concerns/constants.js b/components/x-teaser/src/concerns/constants.js index f388e68d0..eb95ed2a8 100644 --- a/components/x-teaser/src/concerns/constants.js +++ b/components/x-teaser/src/concerns/constants.js @@ -6,15 +6,15 @@ export const ImageSizes = { Large: 420, XL: 640, XXL: 1180 // max width of FT.com page -}; +} export const Layouts = { Small: 'small', Large: 'large', Hero: 'hero', TopStory: 'top-story' -}; +} -export const Newish = 1000 * 60 * 60; +export const Newish = 1000 * 60 * 60 -export const Recent = 1000 * 60 * 60 * 4; +export const Recent = 1000 * 60 * 60 * 4 diff --git a/components/x-teaser/src/concerns/date-time.js b/components/x-teaser/src/concerns/date-time.js index 6273f6cd9..8debc5330 100644 --- a/components/x-teaser/src/concerns/date-time.js +++ b/components/x-teaser/src/concerns/date-time.js @@ -1,4 +1,4 @@ -import { Newish, Recent } from './constants'; +import { Newish, Recent } from './constants' /** * To Date @@ -7,14 +7,14 @@ import { Newish, Recent } from './constants'; */ export function toDate(date) { if (typeof date === 'string') { - return new Date(date); + return new Date(date) } if (typeof date === 'number') { - return new Date(date); + return new Date(date) } - return date; + return date } /** @@ -23,7 +23,7 @@ export function toDate(date) { * @returns {Number} */ export function getRelativeDate(date) { - return Date.now() - toDate(date).getTime(); + return Date.now() - toDate(date).getTime() } /** @@ -35,13 +35,13 @@ export function getRelativeDate(date) { export function getStatus(publishedDate, firstPublishedDate) { if (getRelativeDate(publishedDate) < Newish) { if (publishedDate === firstPublishedDate) { - return 'new'; + return 'new' } else { - return 'updated'; + return 'updated' } } - return ''; + return '' } /** @@ -50,5 +50,5 @@ export function getStatus(publishedDate, firstPublishedDate) { * @returns {Boolean} */ export function isRecent(relativeDate) { - return relativeDate < Recent; + return relativeDate < Recent } diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index cf42141d3..5981e3d2c 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -1,6 +1,6 @@ -const { URL, URLSearchParams } = require('url'); -const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw'; -const OPTIONS = { source:'next', fit:'scale-down', dpr:2 }; +const { URL, URLSearchParams } = require('url') +const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw' +const OPTIONS = { source: 'next', fit: 'scale-down', dpr: 2 } /** * Image Service @@ -9,8 +9,8 @@ const OPTIONS = { source:'next', fit:'scale-down', dpr:2 }; * @param {String} options */ export default function imageService(url, width, options) { - const imageSrc = new URL(`${BASE_URL}/${encodeURIComponent(url)}`); - imageSrc.search = new URLSearchParams({...OPTIONS, ...options }); - imageSrc.searchParams.set('width', width); - return imageSrc.href; + const imageSrc = new URL(`${BASE_URL}/${encodeURIComponent(url)}`) + imageSrc.search = new URLSearchParams({ ...OPTIONS, ...options }) + imageSrc.searchParams.set('width', width) + return imageSrc.href } diff --git a/components/x-teaser/src/concerns/presets.js b/components/x-teaser/src/concerns/presets.js index 4b624628f..b358dec1b 100644 --- a/components/x-teaser/src/concerns/presets.js +++ b/components/x-teaser/src/concerns/presets.js @@ -1,4 +1,4 @@ -import { Layouts } from './constants'; +import { Layouts } from './constants' const Small = { layout: Layouts.Small, @@ -6,7 +6,7 @@ const Small = { showMeta: true, showTitle: true, showStatus: true -}; +} const SmallHeavy = { layout: Layouts.Small, @@ -17,7 +17,7 @@ const SmallHeavy = { showStatus: true, showImage: true, imageSize: 'Small' -}; +} const Large = { layout: Layouts.Large, @@ -28,7 +28,7 @@ const Large = { showStatus: true, showImage: true, imageSize: 'Medium' -}; +} const Hero = { layout: Layouts.Hero, @@ -38,7 +38,7 @@ const Hero = { showStatus: true, showImage: true, imageSize: 'Medium' -}; +} const HeroNarrow = { layout: Layouts.Hero, @@ -47,7 +47,7 @@ const HeroNarrow = { showTitle: true, showStandfirst: true, showStatus: true -}; +} const HeroVideo = { layout: Layouts.Hero, @@ -56,7 +56,7 @@ const HeroVideo = { showTitle: true, showVideo: true, imageSize: 'Large' -}; +} const HeroOverlay = { layout: Layouts.Hero, @@ -67,7 +67,7 @@ const HeroOverlay = { showImage: true, imageSize: 'XL', modifiers: ['hero-image'] -}; +} const TopStory = { layout: Layouts.TopStory, @@ -77,7 +77,7 @@ const TopStory = { showStandfirst: true, showStatus: true, showRelatedLinks: true -}; +} const TopStoryLandscape = { layout: Layouts.TopStory, @@ -90,7 +90,7 @@ const TopStoryLandscape = { imageSize: 'XL', showRelatedLinks: true, modifiers: ['landscape'] -}; +} export default { Small, @@ -102,4 +102,4 @@ export default { HeroOverlay, TopStory, TopStoryLandscape -}; +} diff --git a/components/x-teaser/src/concerns/rules.js b/components/x-teaser/src/concerns/rules.js index ec44de1cf..ed44dfa8a 100644 --- a/components/x-teaser/src/concerns/rules.js +++ b/components/x-teaser/src/concerns/rules.js @@ -6,20 +6,20 @@ const rulesets = { media: (props) => { // If this condition evaluates to true then no headshot nor image will be displayed. if (props.showVideo && props.video && props.video.url) { - return 'video'; + return 'video' } if (props.showHeadshot && props.headshot && props.indicators.isColumn) { - return 'headshot'; + return 'headshot' } if (props.showImage && props.image && props.image.url) { - return 'image'; + return 'image' } }, theme: (props) => { if (props.theme) { - return props.theme; + return props.theme } if (props.status === 'inprogress') { @@ -35,10 +35,10 @@ const rulesets = { } if (props.parentTheme) { - return props.parentTheme; + return props.parentTheme } } -}; +} /** * Rules @@ -48,11 +48,11 @@ const rulesets = { */ export default function rules(rule, props) { if (rulesets.hasOwnProperty(rule)) { - return rulesets[rule](props); + return rulesets[rule](props) } else { - throw Error(`No ruleset available named ${rule}`); + throw Error(`No ruleset available named ${rule}`) } } -export const media = (props) => rules('media', props); -export const theme = (props) => rules('theme', props); +export const media = (props) => rules('media', props) +export const theme = (props) => rules('theme', props) diff --git a/components/x-teaser/storybook/article.js b/components/x-teaser/storybook/article.js index d8cf1533b..b2fac6bff 100644 --- a/components/x-teaser/storybook/article.js +++ b/components/x-teaser/storybook/article.js @@ -1,10 +1,10 @@ -const { presets } = require('../'); +const { presets } = require('../') -exports.title = 'Article'; +exports.title = 'Article' // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/article.json'), presets.SmallHeavy); +exports.data = Object.assign(require('../__fixtures__/article.json'), presets.SmallHeavy) // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -44,8 +44,8 @@ exports.knobs = [ // Variants 'layout', 'modifiers' -]; +] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser/storybook/content-package.js b/components/x-teaser/storybook/content-package.js index 912a59b0a..e5ace4a56 100644 --- a/components/x-teaser/storybook/content-package.js +++ b/components/x-teaser/storybook/content-package.js @@ -1,12 +1,12 @@ -const { presets } = require('../'); +const { presets } = require('../') -exports.title = 'Content Package'; +exports.title = 'Content Package' // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. exports.data = Object.assign(require('../__fixtures__/content-package.json'), presets.Hero, { modifiers: 'centre' -}); +}) // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -38,8 +38,8 @@ exports.knobs = [ 'layout', 'theme', 'modifiers' -]; +] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser/storybook/index.js b/components/x-teaser/storybook/index.js index 4f9e3d5e5..988edef19 100644 --- a/components/x-teaser/storybook/index.js +++ b/components/x-teaser/storybook/index.js @@ -1,8 +1,8 @@ -const { Teaser } = require('../'); +const { Teaser } = require('../') -exports.component = Teaser; +exports.component = Teaser -exports.package = require('../package.json'); +exports.package = require('../package.json') exports.dependencies = { 'o-date': '^4.0.0', @@ -11,7 +11,7 @@ exports.dependencies = { 'o-teaser': '^4.0.0', 'o-typography': '^6.0.0', 'o-video': '^6.0.0' -}; +} exports.stories = [ require('./article'), @@ -22,6 +22,6 @@ exports.stories = [ require('./promoted'), require('./top-story'), require('./video') -]; +] -exports.knobs = require('./knobs'); +exports.knobs = require('./knobs') diff --git a/components/x-teaser/storybook/knobs.js b/components/x-teaser/storybook/knobs.js index 8b185eae7..92cb8cf42 100644 --- a/components/x-teaser/storybook/knobs.js +++ b/components/x-teaser/storybook/knobs.js @@ -13,92 +13,92 @@ module.exports = (data, { object, text, number, boolean, date, select }) => { Indicators: 'Indicators', Context: 'Context', Variant: 'Variant' - }; + } const Features = { showMeta() { - return boolean('Show meta', data.showMeta, Groups.Meta); + return boolean('Show meta', data.showMeta, Groups.Meta) }, showTitle() { - return boolean('Show title', data.showTitle, Groups.Title); + return boolean('Show title', data.showTitle, Groups.Title) }, showStandfirst() { - return boolean('Show standfirst', data.showStandfirst, Groups.Standfirst); + return boolean('Show standfirst', data.showStandfirst, Groups.Standfirst) }, showStatus() { - return boolean('Show status', data.showStatus, Groups.Status); + return boolean('Show status', data.showStatus, Groups.Status) }, showImage() { - return boolean('Show image', data.showImage, Groups.Image); + return boolean('Show image', data.showImage, Groups.Image) }, showHeadshot() { - return boolean('Show headshot', data.showHeadshot, Groups.Headshot); + return boolean('Show headshot', data.showHeadshot, Groups.Headshot) }, showVideo() { - return boolean('Show video', data.showVideo, Groups.Video); + return boolean('Show video', data.showVideo, Groups.Video) }, showRelatedLinks() { - return boolean('Show related links', data.showRelatedLinks, Groups.RelatedLinks); + return boolean('Show related links', data.showRelatedLinks, Groups.RelatedLinks) } - }; + } const Meta = { metaPrefixText() { - return text('Meta prefix text', data.metaPrefixText, Groups.Meta); + return text('Meta prefix text', data.metaPrefixText, Groups.Meta) }, metaSuffixText() { - return text('Meta suffix text', data.metaSuffixText, Groups.Meta); + return text('Meta suffix text', data.metaSuffixText, Groups.Meta) }, metaLink() { return { url: data.metaLink.url, prefLabel: text('Meta link', data.metaLink.prefLabel, Groups.Meta) - }; + } }, metaAltLink() { return { url: data.metaAltLink.url, prefLabel: text('Alt meta link', data.metaAltLink.prefLabel, Groups.Meta) - }; + } }, promotedPrefixText() { - return text('Promoted prefix text', data.promotedPrefixText, Groups.Meta); + return text('Promoted prefix text', data.promotedPrefixText, Groups.Meta) }, promotedSuffixText() { - return text('Promoted suffix text', data.promotedSuffixText, Groups.Meta); + return text('Promoted suffix text', data.promotedSuffixText, Groups.Meta) }, dataTrackable() { - return text('Tracking data', data.dataTrackable, Groups.Meta); + return text('Tracking data', data.dataTrackable, Groups.Meta) } - }; + } const Title = { title() { - return text('Title', data.title, Groups.Title); + return text('Title', data.title, Groups.Title) }, altTitle() { - return text('Alt title', data.altTitle, Groups.Title); + return text('Alt title', data.altTitle, Groups.Title) } - }; + } const Standfirst = { standfirst() { - return text('Standfirst', data.standfirst, Groups.Standfirst); + return text('Standfirst', data.standfirst, Groups.Standfirst) }, altStandfirst() { - return text('Alt standfirst', data.altStandfirst, Groups.Standfirst); + return text('Alt standfirst', data.altStandfirst, Groups.Standfirst) } - }; + } const Status = { publishedDate() { - return date('Published date', new Date(data.publishedDate), Groups.Status); + return date('Published date', new Date(data.publishedDate), Groups.Status) }, firstPublishedDate() { - return date('First published date', new Date(data.firstPublishedDate), Groups.Status); + return date('First published date', new Date(data.firstPublishedDate), Groups.Status) }, useRelativeTime() { - return boolean('Use relative time', data.useRelativeTime, Groups.Status); + return boolean('Use relative time', data.useRelativeTime, Groups.Status) }, status() { return select( @@ -111,9 +111,9 @@ module.exports = (data, { object, text, number, boolean, date, select }) => { }, '', Groups.Status - ); + ) } - }; + } const Image = { image() { @@ -121,24 +121,29 @@ module.exports = (data, { object, text, number, boolean, date, select }) => { url: text('Image URL', data.image.url, Groups.Image), width: number('Image width', data.image.width, {}, Groups.Image), height: number('Image height', data.image.height, {}, Groups.Image) - }; + } }, imageSize() { - return select('Image size', ['XS', 'Small', 'Medium', 'Large', 'XL', 'XXL'], data.imageSize, Groups.Image); - }, - imageHighestQuality(){ - return boolean('Image hi-quality', data.imageHighestQuality, Groups.Image); + return select( + 'Image size', + ['XS', 'Small', 'Medium', 'Large', 'XL', 'XXL'], + data.imageSize, + Groups.Image + ) + }, + imageHighestQuality() { + return boolean('Image hi-quality', data.imageHighestQuality, Groups.Image) } - }; + } const Headshot = { headshot() { - return text('Headshot', data.headshot, Groups.Headshot); + return text('Headshot', data.headshot, Groups.Headshot) }, headshotTint() { - return select('Headshot tint', { 'Default': '' }, 'Default', Groups.Headshot); + return select('Headshot tint', { Default: '' }, 'Default', Groups.Headshot) } - }; + } const Video = { video() { @@ -148,58 +153,73 @@ module.exports = (data, { object, text, number, boolean, date, select }) => { height: number('Video height', data.video.height, {}, Groups.Video), mediaType: data.video.mediaType, codec: data.video.codec - }; + } } - }; + } const RelatedLinks = { relatedLinks() { - return object('Related links', data.relatedLinks, Groups.RelatedLinks); + return object('Related links', data.relatedLinks, Groups.RelatedLinks) } - }; + } const Indicators = { indicators() { return { - accessLevel: select('Access level', ['free', 'registered', 'subscribed', 'premium'], data.indicators.accessLevel || 'free', Groups.Indicators), + accessLevel: select( + 'Access level', + ['free', 'registered', 'subscribed', 'premium'], + data.indicators.accessLevel || 'free', + Groups.Indicators + ), isOpinion: boolean('Is opinion', data.indicators.isOpinion, Groups.Indicators), isColumn: boolean('Is column', data.indicators.isColumn, Groups.Indicators), isPodcast: boolean('Is podcast', data.indicators.isPodcast, Groups.Indicators), - isEditorsChoice: boolean('Is editor\'s choice', data.indicators.isEditorsChoice, Groups.Indicators), + isEditorsChoice: boolean("Is editor's choice", data.indicators.isEditorsChoice, Groups.Indicators), isExclusive: boolean('Is exclusive', data.indicators.isExclusive, Groups.Indicators), isScoop: boolean('Is scoop', data.indicators.isScoop, Groups.Indicators) - }; + } } - }; + } const Context = { headlineTesting() { - return boolean('Headline testing', false, Groups.Context); + return boolean('Headline testing', false, Groups.Context) }, parentId() { - return text('Parent ID', data.context.parentId, Groups.Context); + return text('Parent ID', data.context.parentId, Groups.Context) }, parentLabel() { - return text('Parent Label', data.context.parentLabel, Groups.Context); + return text('Parent Label', data.context.parentLabel, Groups.Context) } - }; + } const Variant = { layout() { - return select('Layout', ['small', 'large', 'hero', 'top-story'], data.layout, Groups.Variant); + return select('Layout', ['small', 'large', 'hero', 'top-story'], data.layout, Groups.Variant) }, theme() { - return select('Theme', { 'None': '', 'Extra': 'extra-article', 'Special Report': 'highlight' }, data.theme, Groups.Variant); + return select( + 'Theme', + { None: '', Extra: 'extra-article', 'Special Report': 'highlight' }, + data.theme, + Groups.Variant + ) }, parentTheme() { - return select('Parent theme', { 'None': '', 'Extra': 'extra-article', 'Special Report': 'highlight' }, data.parentTheme, Groups.Variant); + return select( + 'Parent theme', + { None: '', Extra: 'extra-article', 'Special Report': 'highlight' }, + data.parentTheme, + Groups.Variant + ) }, modifiers() { return select( 'Modifiers', { // Currently no support for optgroups or multiple selections - 'None': '', + None: '', 'Small stacked': 'stacked', 'Small image on right': 'image-on-right', 'Large portrait': 'large-portrait', @@ -211,9 +231,23 @@ module.exports = (data, { object, text, number, boolean, date, select }) => { }, data.modifiers || '', Groups.Variant - ); + ) } - }; + } - return Object.assign({}, Features, Meta, Title, Standfirst, Status, Video, Headshot, Image, RelatedLinks, Indicators, Context, Variant); -}; + return Object.assign( + {}, + Features, + Meta, + Title, + Standfirst, + Status, + Video, + Headshot, + Image, + RelatedLinks, + Indicators, + Context, + Variant + ) +} diff --git a/components/x-teaser/storybook/opinion.js b/components/x-teaser/storybook/opinion.js index 88a34ead2..8cfa62d53 100644 --- a/components/x-teaser/storybook/opinion.js +++ b/components/x-teaser/storybook/opinion.js @@ -1,12 +1,12 @@ -const { presets } = require('../'); +const { presets } = require('../') -exports.title = 'Opinion Piece'; +exports.title = 'Opinion Piece' // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. exports.data = Object.assign(require('../__fixtures__/opinion.json'), presets.SmallHeavy, { showHeadshot: true -}); +}) // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -45,8 +45,8 @@ exports.knobs = [ 'modifiers', // Indicators 'indicators' -]; +] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser/storybook/package-item.js b/components/x-teaser/storybook/package-item.js index 465f454d8..4b65f7505 100644 --- a/components/x-teaser/storybook/package-item.js +++ b/components/x-teaser/storybook/package-item.js @@ -1,13 +1,13 @@ -const { presets } = require('../'); +const { presets } = require('../') -exports.title = 'Package item'; +exports.title = 'Package item' // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. exports.data = Object.assign(require('../__fixtures__/package-item.json'), presets.Hero, { parentTheme: 'extra-article', modifiers: 'centre' -}); +}) // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -44,8 +44,8 @@ exports.knobs = [ 'theme', 'parentTheme', 'modifiers' -]; +] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser/storybook/podcast.js b/components/x-teaser/storybook/podcast.js index 3cd83801e..30075ff06 100644 --- a/components/x-teaser/storybook/podcast.js +++ b/components/x-teaser/storybook/podcast.js @@ -1,10 +1,10 @@ -const { presets } = require('../'); +const { presets } = require('../') -exports.title = 'Podcast'; +exports.title = 'Podcast' // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/podcast.json'), presets.SmallHeavy); +exports.data = Object.assign(require('../__fixtures__/podcast.json'), presets.SmallHeavy) // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -44,8 +44,8 @@ exports.knobs = [ // Variants 'layout', 'modifiers' -]; +] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser/storybook/promoted.js b/components/x-teaser/storybook/promoted.js index 0a0f8a91e..00d54d760 100644 --- a/components/x-teaser/storybook/promoted.js +++ b/components/x-teaser/storybook/promoted.js @@ -1,10 +1,10 @@ -const { presets } = require('../'); +const { presets } = require('../') -exports.title = 'Paid Post'; +exports.title = 'Paid Post' // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/promoted.json'), presets.SmallHeavy); +exports.data = Object.assign(require('../__fixtures__/promoted.json'), presets.SmallHeavy) // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -31,8 +31,8 @@ exports.knobs = [ // Variants 'layout', 'modifiers' -]; +] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser/storybook/top-story.js b/components/x-teaser/storybook/top-story.js index 59ff8b1ac..372496a0a 100644 --- a/components/x-teaser/storybook/top-story.js +++ b/components/x-teaser/storybook/top-story.js @@ -1,10 +1,10 @@ -const { presets } = require('../'); +const { presets } = require('../') -exports.title = 'Top Story'; +exports.title = 'Top Story' // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/top-story.json'), presets.TopStoryLandscape); +exports.data = Object.assign(require('../__fixtures__/top-story.json'), presets.TopStoryLandscape) // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -40,8 +40,8 @@ exports.knobs = [ // Variants 'layout', 'modifiers' -]; +] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/components/x-teaser/storybook/video.js b/components/x-teaser/storybook/video.js index 1fd0137e0..42171e578 100644 --- a/components/x-teaser/storybook/video.js +++ b/components/x-teaser/storybook/video.js @@ -1,10 +1,10 @@ -const { presets } = require('../'); +const { presets } = require('../') -exports.title = 'Video'; +exports.title = 'Video' // This data will provide defaults for the Knobs defined below and used // to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/video.json'), presets.HeroVideo); +exports.data = Object.assign(require('../__fixtures__/video.json'), presets.HeroVideo) // A list of properties to pass to the component when rendered in Storybook. If a Knob // exists for the property then it will be editable with the default as defined above. @@ -38,8 +38,8 @@ exports.knobs = [ // Variants 'layout', 'modifiers' -]; +] // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/jest.config.js b/jest.config.js index 52ac7f513..164aa925f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,4 +8,4 @@ module.exports = { moduleNameMapper: { '^[./a-zA-Z0-9$_-]+\\.scss$': '<rootDir>/__mocks__/styleMock.js' } -}; +} diff --git a/packages/x-babel-config/index.js b/packages/x-babel-config/index.js index 7ef8d01b1..78d65fbbd 100644 --- a/packages/x-babel-config/index.js +++ b/packages/x-babel-config/index.js @@ -29,4 +29,4 @@ module.exports = ({ targets = [], modules = false } = {}) => ({ } ] ] -}); +}) diff --git a/packages/x-babel-config/jest.js b/packages/x-babel-config/jest.js index b6451ee82..1b3475872 100644 --- a/packages/x-babel-config/jest.js +++ b/packages/x-babel-config/jest.js @@ -1,9 +1,9 @@ -const getBabelConfig = require('./'); -const babelJest = require('babel-jest'); +const getBabelConfig = require('./') +const babelJest = require('babel-jest') const base = getBabelConfig({ targets: [{ node: 'current' }], modules: 'commonjs' -}); +}) -module.exports = babelJest.createTransformer(base); +module.exports = babelJest.createTransformer(base) diff --git a/packages/x-engine/src/client.js b/packages/x-engine/src/client.js index 252282893..1e1f06f81 100644 --- a/packages/x-engine/src/client.js +++ b/packages/x-engine/src/client.js @@ -1,9 +1,9 @@ /* eslint no-undef: "off", no-unused-vars: "off" */ // This module is just a placeholder to be re-written at build time. -const runtime = require(X_ENGINE_RUNTIME_MODULE); -const render = require(X_ENGINE_RENDER_MODULE); +const runtime = require(X_ENGINE_RUNTIME_MODULE) +const render = require(X_ENGINE_RENDER_MODULE) -module.exports.h = X_ENGINE_FACTORY; -module.exports.render = X_ENGINE_RENDER; -module.exports.Component = X_ENGINE_COMPONENT; -module.exports.Fragment = X_ENGINE_FRAGMENT; +module.exports.h = X_ENGINE_FACTORY +module.exports.render = X_ENGINE_RENDER +module.exports.Component = X_ENGINE_COMPONENT +module.exports.Fragment = X_ENGINE_FRAGMENT diff --git a/packages/x-engine/src/concerns/deep-get.js b/packages/x-engine/src/concerns/deep-get.js index b83da6a21..b459fe059 100644 --- a/packages/x-engine/src/concerns/deep-get.js +++ b/packages/x-engine/src/concerns/deep-get.js @@ -6,17 +6,17 @@ * @returns {any | null} */ module.exports = (tree, path, defaultValue) => { - const route = path.split('.'); + const route = path.split('.') while (tree !== null && route.length) { - const leaf = route.shift(); + const leaf = route.shift() if (leaf !== undefined && tree.hasOwnProperty(leaf)) { - tree = tree[leaf]; + tree = tree[leaf] } else { - tree = null; + tree = null } } - return tree === null ? defaultValue : tree; -}; + return tree === null ? defaultValue : tree +} diff --git a/packages/x-engine/src/concerns/format-config.js b/packages/x-engine/src/concerns/format-config.js index 695e4c73f..c8a33477c 100644 --- a/packages/x-engine/src/concerns/format-config.js +++ b/packages/x-engine/src/concerns/format-config.js @@ -1,31 +1,31 @@ -const presets = require('./presets'); +const presets = require('./presets') /** * Format Config * @param {string|{ runtime: string, factory?: string }} config * @returns {{ runtime: string, factory: string }} */ -module.exports = function(config) { +module.exports = function (config) { // if configuration is a string, expand it if (typeof config === 'string') { if (presets.hasOwnProperty(config)) { - config = presets[config]; + config = presets[config] } else { - config = { runtime: config, factory: null }; + config = { runtime: config, factory: null } } } if (typeof config.runtime !== 'string') { - throw new TypeError('Engine configuration must define a runtime'); + throw new TypeError('Engine configuration must define a runtime') } if (config.factory && typeof config.factory !== 'string') { - throw new TypeError('Engine factory must be of type String.'); + throw new TypeError('Engine factory must be of type String.') } if (!config.renderModule) { - config.renderModule = config.runtime; + config.renderModule = config.runtime } - return config; -}; + return config +} diff --git a/packages/x-engine/src/concerns/presets.js b/packages/x-engine/src/concerns/presets.js index 399490193..6be7124ef 100644 --- a/packages/x-engine/src/concerns/presets.js +++ b/packages/x-engine/src/concerns/presets.js @@ -20,4 +20,4 @@ module.exports = { fragment: 'Fragment', render: 'render' } -}; +} diff --git a/packages/x-engine/src/concerns/resolve-peer.js b/packages/x-engine/src/concerns/resolve-peer.js index b428f9c70..312de034c 100644 --- a/packages/x-engine/src/concerns/resolve-peer.js +++ b/packages/x-engine/src/concerns/resolve-peer.js @@ -1,2 +1,2 @@ -const path = require('path'); -module.exports = (moduleId) => path.join(process.cwd(), 'node_modules', moduleId); +const path = require('path') +module.exports = (moduleId) => path.join(process.cwd(), 'node_modules', moduleId) diff --git a/packages/x-engine/src/concerns/resolve-pkg.js b/packages/x-engine/src/concerns/resolve-pkg.js index 437386cae..5950986bb 100644 --- a/packages/x-engine/src/concerns/resolve-pkg.js +++ b/packages/x-engine/src/concerns/resolve-pkg.js @@ -1,3 +1,3 @@ -const path = require('path'); +const path = require('path') -module.exports = () => path.join(process.cwd(), 'package.json'); +module.exports = () => path.join(process.cwd(), 'package.json') diff --git a/packages/x-engine/src/server.js b/packages/x-engine/src/server.js index 1bd4193ca..ba3cbfc4d 100644 --- a/packages/x-engine/src/server.js +++ b/packages/x-engine/src/server.js @@ -1,40 +1,40 @@ -const deepGet = require('./concerns/deep-get'); -const resolvePkg = require('./concerns/resolve-pkg'); -const resolvePeer = require('./concerns/resolve-peer'); -const formatConfig = require('./concerns/format-config'); +const deepGet = require('./concerns/deep-get') +const resolvePkg = require('./concerns/resolve-pkg') +const resolvePeer = require('./concerns/resolve-peer') +const formatConfig = require('./concerns/format-config') // 1. try to load the application's package manifest -const pkg = require(resolvePkg()); +const pkg = require(resolvePkg()) // 2. if we have the manifest then find the engine configuration -const raw = deepGet(pkg, 'x-dash.engine.server'); +const raw = deepGet(pkg, 'x-dash.engine.server') if (!raw) { - throw new Error(`x-engine requires a server runtime to be specified. none found in ${pkg.name}`); + throw new Error(`x-engine requires a server runtime to be specified. none found in ${pkg.name}`) } // 3. format the configuration we've loaded -const config = formatConfig(raw); +const config = formatConfig(raw) // 4. if this module is a linked dependency then resolve required runtime to CWD -const runtime = require(resolvePeer(config.runtime)); +const runtime = require(resolvePeer(config.runtime)) // 5. if we've loaded the runtime then find its create element factory function -const factory = config.factory ? runtime[config.factory] : runtime; +const factory = config.factory ? runtime[config.factory] : runtime // 6. if we've loaded the runtime then find its Component constructor -const component = config.component ? runtime[config.component] : null; +const component = config.component ? runtime[config.component] : null // 7. if we've loaded the runtime then find its Fragment object -const fragment = config.fragment ? runtime[config.fragment] : null; +const fragment = config.fragment ? runtime[config.fragment] : null // 8. if the rendering module is different to the runtime, load it -const renderModule = config.renderModule ? require(resolvePeer(config.renderModule)) : runtime; +const renderModule = config.renderModule ? require(resolvePeer(config.renderModule)) : runtime // 9. if we've got the render module then find its render method -const render = config.render ? renderModule[config.render] : renderModule; +const render = config.render ? renderModule[config.render] : renderModule -module.exports.h = factory; -module.exports.render = render; -module.exports.Component = component; -module.exports.Fragment = fragment; +module.exports.h = factory +module.exports.render = render +module.exports.Component = component +module.exports.Fragment = fragment diff --git a/packages/x-engine/src/webpack.js b/packages/x-engine/src/webpack.js index 4aaa656d9..308529334 100644 --- a/packages/x-engine/src/webpack.js +++ b/packages/x-engine/src/webpack.js @@ -1,29 +1,29 @@ -const assignDeep = require('assign-deep'); -const deepGet = require('./concerns/deep-get'); -const resolvePkg = require('./concerns/resolve-pkg'); -const resolvePeer = require('./concerns/resolve-peer'); -const formatConfig = require('./concerns/format-config'); +const assignDeep = require('assign-deep') +const deepGet = require('./concerns/deep-get') +const resolvePkg = require('./concerns/resolve-pkg') +const resolvePeer = require('./concerns/resolve-peer') +const formatConfig = require('./concerns/format-config') -module.exports = function() { +module.exports = function () { // 1. try to load the application's package manifest - const pkg = require(resolvePkg()); + const pkg = require(resolvePkg()) // 2. if we have the manifest then find the engine configuration - const raw = deepGet(pkg, 'x-dash.engine.browser'); + const raw = deepGet(pkg, 'x-dash.engine.browser') if (!raw) { - throw new Error(`x-engine requires a browser runtime to be specified. none found in ${pkg.name}`); + throw new Error(`x-engine requires a browser runtime to be specified. none found in ${pkg.name}`) } // 3. format the configuration we've loaded - const config = formatConfig(raw); + const config = formatConfig(raw) // 4. if this module is a linked dependency then resolve Webpack & runtime to CWD - const webpackResolution = resolvePeer('webpack'); - const runtimeResolution = resolvePeer(config.runtime); - const renderResolution = resolvePeer(config.renderModule); + const webpackResolution = resolvePeer('webpack') + const runtimeResolution = resolvePeer(config.runtime) + const renderResolution = resolvePeer(config.renderModule) - const webpack = require(webpackResolution); + const webpack = require(webpackResolution) return { apply(compiler) { @@ -32,25 +32,25 @@ module.exports = function() { resolve: { alias: { [config.runtime]: runtimeResolution, - [config.renderModule]: renderResolution, - }, - }, - }); + [config.renderModule]: renderResolution + } + } + }) const replacements = { - 'X_ENGINE_RUNTIME_MODULE': `"${config.runtime}"`, - 'X_ENGINE_FACTORY': config.factory ? `runtime["${config.factory}"]` : 'runtime', - 'X_ENGINE_COMPONENT': config.component ? `runtime["${config.component}"]` : 'null', - 'X_ENGINE_FRAGMENT': config.fragment ? `runtime["${config.fragment}"]` : 'null', - 'X_ENGINE_RENDER_MODULE': `"${config.renderModule}"`, - 'X_ENGINE_RENDER': config.render ? `render["${config.render}"]` : 'null', - }; + X_ENGINE_RUNTIME_MODULE: `"${config.runtime}"`, + X_ENGINE_FACTORY: config.factory ? `runtime["${config.factory}"]` : 'runtime', + X_ENGINE_COMPONENT: config.component ? `runtime["${config.component}"]` : 'null', + X_ENGINE_FRAGMENT: config.fragment ? `runtime["${config.fragment}"]` : 'null', + X_ENGINE_RENDER_MODULE: `"${config.renderModule}"`, + X_ENGINE_RENDER: config.render ? `render["${config.render}"]` : 'null' + } // The define plugin performs direct text replacement // <https://webpack.js.org/plugins/define-plugin/> - const define = new webpack.DefinePlugin(replacements); + const define = new webpack.DefinePlugin(replacements) - define.apply(compiler); + define.apply(compiler) } - }; -}; + } +} diff --git a/packages/x-handlebars/concerns/resolve-local.js b/packages/x-handlebars/concerns/resolve-local.js index f3170513e..2ce5365ba 100644 --- a/packages/x-handlebars/concerns/resolve-local.js +++ b/packages/x-handlebars/concerns/resolve-local.js @@ -1,2 +1,2 @@ -const path = require('path'); -module.exports = (baseDirectory, moduleId) => path.resolve(baseDirectory, moduleId); +const path = require('path') +module.exports = (baseDirectory, moduleId) => path.resolve(baseDirectory, moduleId) diff --git a/packages/x-handlebars/concerns/resolve-peer.js b/packages/x-handlebars/concerns/resolve-peer.js index b428f9c70..312de034c 100644 --- a/packages/x-handlebars/concerns/resolve-peer.js +++ b/packages/x-handlebars/concerns/resolve-peer.js @@ -1,2 +1,2 @@ -const path = require('path'); -module.exports = (moduleId) => path.join(process.cwd(), 'node_modules', moduleId); +const path = require('path') +module.exports = (moduleId) => path.join(process.cwd(), 'node_modules', moduleId) diff --git a/packages/x-handlebars/index.js b/packages/x-handlebars/index.js index 3959605fa..4ce57ee17 100644 --- a/packages/x-handlebars/index.js +++ b/packages/x-handlebars/index.js @@ -1,56 +1,62 @@ -const { h, render } = require('@financial-times/x-engine'); -const resolvePeer = require('./concerns/resolve-peer'); -const resolveLocal = require('./concerns/resolve-local'); +const { h, render } = require('@financial-times/x-engine') +const resolvePeer = require('./concerns/resolve-peer') +const resolveLocal = require('./concerns/resolve-local') const defaults = { baseDirectory: process.cwd() -}; +} // We're exporting a function in case we need to add options or similar features later module.exports = (userOptions = {}) => { - const options = Object.assign({}, defaults, userOptions); + const options = Object.assign({}, defaults, userOptions) // Return a regular function expression so that the template context may be shared (this) - return function({ hash }) { - let moduleId; + return function ({ hash }) { + let moduleId if (hash.hasOwnProperty('package')) { - moduleId = resolvePeer(`@financial-times/${hash.package}`); + moduleId = resolvePeer(`@financial-times/${hash.package}`) } if (hash.hasOwnProperty('local')) { - moduleId = resolveLocal(options.baseDirectory, `./${hash.local}`); + moduleId = resolveLocal(options.baseDirectory, `./${hash.local}`) } if (!moduleId) { - throw new Error('You must specify a "package" or "local" argument to load a component'); + throw new Error('You must specify a "package" or "local" argument to load a component') } - const target = require(moduleId); + const target = require(moduleId) // TODO: remove this mixin stuff and make the components more easily configurable! - const mixins = {}; + const mixins = {} if (hash.hasOwnProperty('preset') && target.hasOwnProperty('presets')) { - Object.assign(mixins, target.presets[hash.preset]); + Object.assign(mixins, target.presets[hash.preset]) } - const component = hash.hasOwnProperty('component') ? target[hash.component] : target; + const component = hash.hasOwnProperty('component') ? target[hash.component] : target - const type = typeof component; + const type = typeof component if (type !== 'function') { - throw TypeError(`The included component (${hash.component} from ${hash.local || hash.package}) is not a function, it is of type "${type}"`); + throw TypeError( + `The included component (${hash.component} from ${ + hash.local || hash.package + }) is not a function, it is of type "${type}"` + ) } - const props = Object.assign({}, this, mixins, hash); + const props = Object.assign({}, this, mixins, hash) // Don't allow implementors to pass in the root context when using Express as the "locals" object may include sensitive data. // <https://github.com/expressjs/express/blob/0a48e18056865364b2461b2ece7ccb2d1075d3c9/lib/response.js#L1002-L1003> if (props.hasOwnProperty('_locals')) { - throw new Error(`The root handlebars context shouldn't be passed to a component, as it may contain sensitive data.`); + throw new Error( + `The root handlebars context shouldn't be passed to a component, as it may contain sensitive data.` + ) } - return render(h(component, props) || ''); - }; -}; + return render(h(component, props) || '') + } +} diff --git a/packages/x-logo/src/color.js b/packages/x-logo/src/color.js index bdf1f7be7..cede4ab00 100644 --- a/packages/x-logo/src/color.js +++ b/packages/x-logo/src/color.js @@ -1,20 +1,20 @@ -import { hsluvToHex } from 'hsluv'; +import { hsluvToHex } from 'hsluv' export default (shift, random) => { // Start a hue rotation (0-360 degrees) from a random position - const hue = random() * 360; + const hue = random() * 360 // Make a starting HSL color - const hues = [hue, hue + shift, hue - shift, hue + shift * 2]; + const hues = [hue, hue + shift, hue - shift, hue + shift * 2] // Return a function to generate a color for a given coordinate return ([x, y]) => { - const [tl, tr, bl, br] = hues; - const th = tl + (tr - tl) * (x / 100); - const bh = bl + (br - bl) * (x / 100); - const hue = th + (bh - th) * (y / 100); + const [tl, tr, bl, br] = hues + const th = tl + (tr - tl) * (x / 100) + const bh = bl + (br - bl) * (x / 100) + const hue = th + (bh - th) * (y / 100) // <http://www.hsluv.org/> - return hsluvToHex([hue, 85 + 10 * random(), 45 + 10 * random()]); - }; -}; + return hsluvToHex([hue, 85 + 10 * random(), 45 + 10 * random()]) + } +} diff --git a/packages/x-logo/src/index.js b/packages/x-logo/src/index.js index 264dfd26f..5e3313cbc 100644 --- a/packages/x-logo/src/index.js +++ b/packages/x-logo/src/index.js @@ -1,20 +1,20 @@ -import React from 'react'; -import seedrandom from 'seedrandom'; -import createColor from './color'; -import createNoise from './noise'; -import createPolygon from './polygon'; -import createTriangles from './triangles'; -import { pointsToString } from './util'; +import React from 'react' +import seedrandom from 'seedrandom' +import createColor from './color' +import createNoise from './noise' +import createPolygon from './polygon' +import createTriangles from './triangles' +import { pointsToString } from './util' const options = { seed: Math.random(), density: 15, thickness: 16, hueShift: 45 -}; +} // Create a random number generator -const random = seedrandom(options.seed); +const random = seedrandom(options.seed) // Create the standard size X for use as the clip mask const polygonPoints = createPolygon({ @@ -23,7 +23,7 @@ const polygonPoints = createPolygon({ width: 100, height: 100, thickness: options.thickness -}); +}) // Create a larger X "canvas" to place points and shapes within const polygonCanvas = createPolygon({ @@ -32,7 +32,7 @@ const polygonCanvas = createPolygon({ width: 150, height: 150, thickness: options.thickness * 1.25 -}); +}) const animations = ` @keyframes logoHueRotate { @@ -45,19 +45,19 @@ const animations = ` 50% { opacity: 0.8; } 100% { opacity: 1; } } -`; +` // Create random points within the given canvas and with the given seed -const noise = createNoise(options.density, polygonCanvas, random); +const noise = createNoise(options.density, polygonCanvas, random) // Join the random points to create a set of triangles -const triangles = createTriangles(noise); +const triangles = createTriangles(noise) // Create a random color generator from the given hue and seed -const getColor = createColor(options.hueShift, random); +const getColor = createColor(options.hueShift, random) // Create an array to iterate over to draw each triangle -const numberOfTriangles = Array.from(Array(triangles.length / 3).keys()); +const numberOfTriangles = Array.from(Array(triangles.length / 3).keys()) export default () => ( <React.Fragment> @@ -68,20 +68,15 @@ export default () => ( style={{ animation: 'logoHueRotate 30s linear infinite' }}> - <clipPath id="logo-clip-path"> <polygon points={pointsToString(polygonPoints)} /> </clipPath> <g clipPath="url(#logo-clip-path)"> {numberOfTriangles.map((i) => { - const points = [ - noise[triangles[i * 3]], - noise[triangles[i * 3 + 1]], - noise[triangles[i * 3 + 2]] - ]; + const points = [noise[triangles[i * 3]], noise[triangles[i * 3 + 1]], noise[triangles[i * 3 + 2]]] - const color = getColor(noise[triangles[i * 3]]); + const color = getColor(noise[triangles[i * 3]]) return ( <polygon @@ -95,9 +90,9 @@ export default () => ( animation: `logoShimmer ${(random() * 10 + 5).toFixed(2)}s linear infinite` }} /> - ); + ) })} </g> </svg> </React.Fragment> -); +) diff --git a/packages/x-logo/src/noise.js b/packages/x-logo/src/noise.js index 29db3f030..c77caf971 100644 --- a/packages/x-logo/src/noise.js +++ b/packages/x-logo/src/noise.js @@ -1,14 +1,14 @@ -import Poisson from 'poisson-disk-sampling'; -import pointInPolygon from 'point-in-polygon'; +import Poisson from 'poisson-disk-sampling' +import pointInPolygon from 'point-in-polygon' export default (density, canvas, random) => { // Poisson noise produces randomly distributed points that may not be too close to each other // <https://en.wikipedia.org/wiki/Shot_noise> - const noise = new Poisson([150, 150], 100 / density, 100, 30, random); + const noise = new Poisson([150, 150], 100 / density, 100, 30, random) // Remove any points that do not fit within the given canvas return noise .fill() .map(([x, y]) => [x - 25, y - 25]) - .filter((point) => pointInPolygon(point, canvas)); -}; + .filter((point) => pointInPolygon(point, canvas)) +} diff --git a/packages/x-logo/src/polygon.js b/packages/x-logo/src/polygon.js index 332d747a1..7bd4547b3 100644 --- a/packages/x-logo/src/polygon.js +++ b/packages/x-logo/src/polygon.js @@ -1,7 +1,7 @@ // Returns an array of points for an X shaped polygon export default ({ x, y, width, height, thickness }) => { - const middleX = x + width / 2; - const middleY = y + height / 2; + const middleX = x + width / 2 + const middleY = y + height / 2 return [ [x, y + thickness], @@ -16,5 +16,5 @@ export default ({ x, y, width, height, thickness }) => { [x + width - thickness, y], [middleX, middleY - thickness], [x + thickness, y] - ]; -}; + ] +} diff --git a/packages/x-logo/src/triangles.js b/packages/x-logo/src/triangles.js index cfd1b8087..e3e723f1f 100644 --- a/packages/x-logo/src/triangles.js +++ b/packages/x-logo/src/triangles.js @@ -1,6 +1,6 @@ -import Delaunay from 'delaunator'; +import Delaunay from 'delaunator' export default (noise) => { - const { triangles } = Delaunay.from(noise); - return triangles; -}; + const { triangles } = Delaunay.from(noise) + return triangles +} diff --git a/packages/x-logo/src/util.js b/packages/x-logo/src/util.js index c1e13095d..97dc55e5a 100644 --- a/packages/x-logo/src/util.js +++ b/packages/x-logo/src/util.js @@ -1,4 +1,3 @@ // Stringifies a list points // <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/points> -export const pointsToString = (points) => - points.map((point) => point.map((x) => Math.round(x)).join()).join(); +export const pointsToString = (points) => points.map((point) => point.map((x) => Math.round(x)).join()).join() diff --git a/packages/x-node-jsx/index.js b/packages/x-node-jsx/index.js index 99824cafd..14993d99f 100644 --- a/packages/x-node-jsx/index.js +++ b/packages/x-node-jsx/index.js @@ -1,29 +1,29 @@ -const { addHook } = require('pirates'); -const { transform } = require('sucrase'); +const { addHook } = require('pirates') +const { transform } = require('sucrase') -const extension = '.jsx'; +const extension = '.jsx' // Assume .jsx components are using x-engine const jsxOptions = { jsxPragma: 'h', jsxFragmentPragma: 'Fragment' -}; +} const defaultOptions = { // Do not output JSX debugger information production: true, // https://github.com/alangpierce/sucrase#transforms transforms: ['imports', 'jsx'] -}; +} module.exports = (userOptions = {}) => { - const options = { ...defaultOptions, ...userOptions, ...jsxOptions }; + const options = { ...defaultOptions, ...userOptions, ...jsxOptions } const handleJSX = (code) => { - const transformed = transform(code, options); - return transformed.code; - }; + const transformed = transform(code, options) + return transformed.code + } // Return a function to revert the hook - return addHook(handleJSX, { exts: [extension] }); -}; + return addHook(handleJSX, { exts: [extension] }) +} diff --git a/packages/x-node-jsx/register.js b/packages/x-node-jsx/register.js index f73e3bfa7..2b8efefce 100644 --- a/packages/x-node-jsx/register.js +++ b/packages/x-node-jsx/register.js @@ -1 +1 @@ -require('./')(); +require('./')() diff --git a/packages/x-rollup/index.js b/packages/x-rollup/index.js index aa5d51258..1e24dce92 100644 --- a/packages/x-rollup/index.js +++ b/packages/x-rollup/index.js @@ -1,16 +1,16 @@ -const rollupConfig = require('./src/rollup-config'); -const logger = require('./src/logger'); -const bundle = require('./src/bundle'); -const watch = require('./src/watch'); +const rollupConfig = require('./src/rollup-config') +const logger = require('./src/logger') +const bundle = require('./src/bundle') +const watch = require('./src/watch') module.exports = async (options) => { - const configs = rollupConfig(options); - const command = process.argv.slice(-1)[0] === '--watch' ? watch : bundle; + const configs = rollupConfig(options) + const command = process.argv.slice(-1)[0] === '--watch' ? watch : bundle try { - await command(configs); + await command(configs) } catch (error) { - logger.error(error instanceof Error ? error.message : error); - process.exit(1); + logger.error(error instanceof Error ? error.message : error) + process.exit(1) } -}; +} diff --git a/packages/x-rollup/src/babel-config.js b/packages/x-rollup/src/babel-config.js index 93f51c766..fe6ba7ffd 100644 --- a/packages/x-rollup/src/babel-config.js +++ b/packages/x-rollup/src/babel-config.js @@ -1,15 +1,15 @@ -const baseBabelConfig = require('@financial-times/x-babel-config'); +const baseBabelConfig = require('@financial-times/x-babel-config') module.exports = (...args) => { - const base = baseBabelConfig(...args); + const base = baseBabelConfig(...args) base.plugins.push( // Instruct Babel to not include any internal helper declarations in the output - require.resolve('@babel/plugin-external-helpers'), - ); + require.resolve('@babel/plugin-external-helpers') + ) // rollup-specific option not included in base config - base.include = '**/*.{js,jsx}'; + base.include = '**/*.{js,jsx}' - return base; + return base } diff --git a/packages/x-rollup/src/bundle.js b/packages/x-rollup/src/bundle.js index c9fed0d78..69f9d1f6c 100644 --- a/packages/x-rollup/src/bundle.js +++ b/packages/x-rollup/src/bundle.js @@ -1,10 +1,10 @@ -const rollup = require('rollup'); -const logger = require('./logger'); +const rollup = require('rollup') +const logger = require('./logger') module.exports = async (configs) => { for (const [input, output] of configs) { - const bundle = await rollup.rollup(input); - await bundle.write(output); - logger.success(`Bundled ${output.file}`); + const bundle = await rollup.rollup(input) + await bundle.write(output) + logger.success(`Bundled ${output.file}`) } -}; +} diff --git a/packages/x-rollup/src/logger.js b/packages/x-rollup/src/logger.js index a0cd0c6d3..f5cf35b73 100644 --- a/packages/x-rollup/src/logger.js +++ b/packages/x-rollup/src/logger.js @@ -1,27 +1,27 @@ -const chalk = require('chalk'); -const logSymbols = require('log-symbols'); +const chalk = require('chalk') +const logSymbols = require('log-symbols') const format = (symbol, color, message) => { - const time = new Date().toLocaleTimeString(); - return `[${time}] ${symbol} ${chalk[color](message)}\n`; -}; + const time = new Date().toLocaleTimeString() + return `[${time}] ${symbol} ${chalk[color](message)}\n` +} module.exports.info = (message) => { - process.stdout.write(format(logSymbols.info, 'blue', message)); -}; + process.stdout.write(format(logSymbols.info, 'blue', message)) +} module.exports.message = (message) => { - process.stdout.write(format('\x20', 'gray', message)); -}; + process.stdout.write(format('\x20', 'gray', message)) +} module.exports.success = (message) => { - process.stdout.write(format(logSymbols.success, 'green', message)); -}; + process.stdout.write(format(logSymbols.success, 'green', message)) +} module.exports.warning = (message) => { - process.stdout.write(format(logSymbols.warning, 'yellow', message)); -}; + process.stdout.write(format(logSymbols.warning, 'yellow', message)) +} module.exports.error = (message) => { - process.stderr.write(format(logSymbols.error, 'red', message)); -}; + process.stderr.write(format(logSymbols.error, 'red', message)) +} diff --git a/packages/x-rollup/src/postcss-config.js b/packages/x-rollup/src/postcss-config.js index 5fac57486..e554a7b35 100644 --- a/packages/x-rollup/src/postcss-config.js +++ b/packages/x-rollup/src/postcss-config.js @@ -1,4 +1,4 @@ -const path = require('path'); +const path = require('path') module.exports = (style) => { return { @@ -14,5 +14,5 @@ module.exports = (style) => { 'stylus', 'less' ] - }; -}; + } +} diff --git a/packages/x-rollup/src/rollup-config.js b/packages/x-rollup/src/rollup-config.js index 0c652de13..2ce61e61f 100644 --- a/packages/x-rollup/src/rollup-config.js +++ b/packages/x-rollup/src/rollup-config.js @@ -1,22 +1,22 @@ -const babel = require('rollup-plugin-babel'); -const postcss = require('rollup-plugin-postcss'); -const commonjs = require('rollup-plugin-commonjs'); -const postcssConfig = require('./postcss-config'); -const babelConfig = require('./babel-config'); +const babel = require('rollup-plugin-babel') +const postcss = require('rollup-plugin-postcss') +const commonjs = require('rollup-plugin-commonjs') +const postcssConfig = require('./postcss-config') +const babelConfig = require('./babel-config') module.exports = ({ input, pkg }) => { // Don't bundle any dependencies - const external = Object.keys(pkg.dependencies); + const external = Object.keys(pkg.dependencies) const plugins = [ // Convert CommonJS modules to ESM so they can be included in the bundle commonjs({ extensions: ['.js', '.jsx'] }) - ]; + ] // Add support for CSS modules (and any required transpilation) if (pkg.style) { - const config = postcssConfig(pkg.style); - plugins.push(postcss(config)); + const config = postcssConfig(pkg.style) + plugins.push(postcss(config)) } // Pairs of input and output options @@ -26,9 +26,11 @@ module.exports = ({ input, pkg }) => { input, external, plugins: [ - babel(babelConfig({ - targets: [{ node: 6 }] - })), + babel( + babelConfig({ + targets: [{ node: 6 }] + }) + ), ...plugins ] }, @@ -42,9 +44,11 @@ module.exports = ({ input, pkg }) => { input, external, plugins: [ - babel(babelConfig({ - targets: [{ node: 6 }] - })), + babel( + babelConfig({ + targets: [{ node: 6 }] + }) + ), ...plugins ] }, @@ -58,9 +62,11 @@ module.exports = ({ input, pkg }) => { input, external, plugins: [ - babel(babelConfig({ - targets: [{ browsers: ['ie 11'] }] - })), + babel( + babelConfig({ + targets: [{ browsers: ['ie 11'] }] + }) + ), ...plugins ] }, @@ -69,5 +75,5 @@ module.exports = ({ input, pkg }) => { format: 'cjs' } ] - ]; -}; + ] +} diff --git a/packages/x-rollup/src/watch.js b/packages/x-rollup/src/watch.js index e4eebf705..be9199b79 100644 --- a/packages/x-rollup/src/watch.js +++ b/packages/x-rollup/src/watch.js @@ -1,36 +1,36 @@ -const path = require('path'); -const rollup = require('rollup'); -const logger = require('./logger'); +const path = require('path') +const rollup = require('rollup') +const logger = require('./logger') module.exports = (configs) => { // Merge the separate input/output options for each bundle const formattedConfigs = configs.map(([input, output]) => { - return { ...input, output }; - }); + return { ...input, output } + }) return new Promise((resolve, reject) => { - const watcher = rollup.watch(formattedConfigs); + const watcher = rollup.watch(formattedConfigs) - logger.info('Watching files, press ctrl + c to stop'); + logger.info('Watching files, press ctrl + c to stop') watcher.on('event', (event) => { switch (event.code) { case 'END': - logger.message('Waiting for changes…'); - break; + logger.message('Waiting for changes…') + break case 'BUNDLE_END': - logger.success(`Bundled ${path.relative(process.cwd(), event.output[0])}`); - break; + logger.success(`Bundled ${path.relative(process.cwd(), event.output[0])}`) + break case 'ERROR': - logger.warning(event.error); - break; + logger.warning(event.error) + break case 'FATAL': - reject(event.error); - break; + reject(event.error) + break } - }); - }); -}; + }) + }) +} diff --git a/packages/x-test-utils/enzyme.js b/packages/x-test-utils/enzyme.js index 571b0a790..f840a1ddc 100644 --- a/packages/x-test-utils/enzyme.js +++ b/packages/x-test-utils/enzyme.js @@ -1,7 +1,7 @@ -const Enzyme = require('enzyme'); -const Adapter = require('enzyme-adapter-react-16'); -require('jest-enzyme'); +const Enzyme = require('enzyme') +const Adapter = require('enzyme-adapter-react-16') +require('jest-enzyme') -Enzyme.configure({ adapter: new Adapter() }); +Enzyme.configure({ adapter: new Adapter() }) -module.exports = Enzyme; +module.exports = Enzyme diff --git a/private/blueprints/component/rollup.js b/private/blueprints/component/rollup.js index dd7f2fb3f..3dbac7a40 100644 --- a/private/blueprints/component/rollup.js +++ b/private/blueprints/component/rollup.js @@ -1,4 +1,4 @@ -const xRollup = require('@financial-times/x-rollup'); -const pkg = require('./package.json'); +const xRollup = require('@financial-times/x-rollup') +const pkg = require('./package.json') -xRollup({ input: './src/{{componentName}}.jsx', pkg }); +xRollup({ input: './src/{{componentName}}.jsx', pkg }) diff --git a/private/blueprints/component/stories/example.js b/private/blueprints/component/stories/example.js index 6df941bf7..05cb1c9d9 100644 --- a/private/blueprints/component/stories/example.js +++ b/private/blueprints/component/stories/example.js @@ -1,9 +1,9 @@ -exports.title = 'Example'; +exports.title = 'Example' exports.data = { message: 'Hello World!' -}; +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module; +exports.m = module diff --git a/private/scripts/blueprint.js b/private/scripts/blueprint.js index adca45ef8..d2551bcb9 100644 --- a/private/scripts/blueprint.js +++ b/private/scripts/blueprint.js @@ -1,99 +1,99 @@ /* eslint no-console:off */ -const fs = require('fs'); -const path = require('path'); +const fs = require('fs') +const path = require('path') function template(source, data = {}) { - const token = /\{\{(\w+)\}\}/g; - return source.replace(token, (match, prop) => data[prop] || ''); + const token = /\{\{(\w+)\}\}/g + return source.replace(token, (match, prop) => data[prop] || '') } // Recursively load files from the target directory function loadFiles(target) { - const fileNames = fs.readdirSync(target); - const output = {}; + const fileNames = fs.readdirSync(target) + const output = {} for (const fileName of fileNames) { - const fullPath = path.join(target, fileName); - const stats = fs.statSync(fullPath); + const fullPath = path.join(target, fileName) + const stats = fs.statSync(fullPath) if (stats.isDirectory()) { - output[fileName] = loadFiles(fullPath); + output[fileName] = loadFiles(fullPath) } else { - output[fileName] = String(fs.readFileSync(fullPath)); + output[fileName] = String(fs.readFileSync(fullPath)) } } - return output; + return output } // Run each file through simple templating function templateFiles(files = {}, data = {}) { - const output = {}; + const output = {} for (const [file, contents] of Object.entries(files)) { if (typeof contents === 'object') { - output[file] = templateFiles(contents, data); + output[file] = templateFiles(contents, data) } else { // allow file names to include placeholders - const fileName = template(file, data); - output[fileName] = template(contents, data); + const fileName = template(file, data) + output[fileName] = template(contents, data) } } - return output; + return output } function writeOutput(target, output) { - const relPath = path.relative(process.cwd(), target); - console.log(`Creating directory ${relPath}`); + const relPath = path.relative(process.cwd(), target) + console.log(`Creating directory ${relPath}`) - fs.mkdirSync(target); + fs.mkdirSync(target) for (const [file, contents] of Object.entries(output)) { - const fullPath = path.join(target, file); + const fullPath = path.join(target, file) if (typeof contents === 'object') { - writeOutput(fullPath, contents); + writeOutput(fullPath, contents) } else { - console.log(`Creating file ${file}`); + console.log(`Creating file ${file}`) - fs.writeFileSync(fullPath, contents); + fs.writeFileSync(fullPath, contents) } } } function fatal(message) { - console.error(`ERROR: ${message}`); - process.exit(1); + console.error(`ERROR: ${message}`) + process.exit(1) } // Collate variables -const name = process.argv.slice(-1).pop(); -const formattedName = typeof name === 'string' ? name.replace(/[^a-z]/i, '') : ''; -const packageName = `x-${formattedName.toLowerCase()}`; -const componentName = formattedName.charAt(0).toUpperCase() + name.substr(1); -const sourceDir = path.join(process.cwd(), 'private/blueprints/component'); -const targetDir = path.join(process.cwd(), 'components', packageName); +const name = process.argv.slice(-1).pop() +const formattedName = typeof name === 'string' ? name.replace(/[^a-z]/i, '') : '' +const packageName = `x-${formattedName.toLowerCase()}` +const componentName = formattedName.charAt(0).toUpperCase() + name.substr(1) +const sourceDir = path.join(process.cwd(), 'private/blueprints/component') +const targetDir = path.join(process.cwd(), 'components', packageName) // Validate input if (name === undefined) { - fatal('A component name is required, usage: blueprint.js {name}'); + fatal('A component name is required, usage: blueprint.js {name}') } if (/^x/.test(name)) { - fatal('Component names should not include the "x-" prefix'); + fatal('Component names should not include the "x-" prefix') } if (fs.existsSync(targetDir)) { - fatal(`Directory ${targetDir} already exists`); + fatal(`Directory ${targetDir} already exists`) } // Create and write blueprint files try { - const files = loadFiles(sourceDir); - const templated = templateFiles(files, { packageName, componentName }); + const files = loadFiles(sourceDir) + const templated = templateFiles(files, { packageName, componentName }) - writeOutput(targetDir, templated); + writeOutput(targetDir, templated) } catch (error) { - fatal(error.message); + fatal(error.message) } diff --git a/web/gatsby-config.js b/web/gatsby-config.js index 26e347579..a600acd55 100644 --- a/web/gatsby-config.js +++ b/web/gatsby-config.js @@ -43,11 +43,7 @@ module.exports = { { resolve: 'gatsby-transformer-remark', options: { - plugins: [ - 'gatsby-remark-prismjs', - 'gatsby-remark-autolink-headers', - 'gatsby-remark-external-links' - ] + plugins: ['gatsby-remark-prismjs', 'gatsby-remark-autolink-headers', 'gatsby-remark-external-links'] } }, // Handles package manifest files (creates "NpmPackage" nodes) @@ -55,4 +51,4 @@ module.exports = { // Handles YAML files (creates "YourFileNameYaml" nodes) 'gatsby-transformer-yaml' ] -}; +} diff --git a/web/gatsby-node.js b/web/gatsby-node.js index 93f3c2286..ce52d061d 100644 --- a/web/gatsby-node.js +++ b/web/gatsby-node.js @@ -1,14 +1,11 @@ -const decorateNodes = require('./src/lib/decorate-nodes'); -const createNpmPackagePages = require('./src/lib/create-npm-package-pages'); -const createDocumentationPages = require('./src/lib/create-documentation-pages'); +const decorateNodes = require('./src/lib/decorate-nodes') +const createNpmPackagePages = require('./src/lib/create-npm-package-pages') +const createDocumentationPages = require('./src/lib/create-documentation-pages') exports.onCreateNode = ({ node, actions, getNode }) => { - decorateNodes(node, actions, getNode); -}; + decorateNodes(node, actions, getNode) +} exports.createPages = async ({ actions, graphql }) => { - return Promise.all([ - createNpmPackagePages(actions, graphql), - createDocumentationPages(actions, graphql) - ]); -}; + return Promise.all([createNpmPackagePages(actions, graphql), createDocumentationPages(actions, graphql)]) +} diff --git a/web/plugins/gatsby-transformer-npm-package/extend-node-type.js b/web/plugins/gatsby-transformer-npm-package/extend-node-type.js index 3ff79d7ee..29cacf455 100644 --- a/web/plugins/gatsby-transformer-npm-package/extend-node-type.js +++ b/web/plugins/gatsby-transformer-npm-package/extend-node-type.js @@ -1,4 +1,4 @@ -const { GraphQLJSONObject } = require('graphql-type-json'); +const { GraphQLJSONObject } = require('graphql-type-json') // This allows us to fetch the entire manifest without specifying every field \0/ module.exports = ({ type }) => { @@ -8,6 +8,6 @@ module.exports = ({ type }) => { type: GraphQLJSONObject, resolve: (node) => node.manifest } - }; + } } -}; +} diff --git a/web/plugins/gatsby-transformer-npm-package/gatsby-node.js b/web/plugins/gatsby-transformer-npm-package/gatsby-node.js index 8a140cc2a..87ffb1e7b 100644 --- a/web/plugins/gatsby-transformer-npm-package/gatsby-node.js +++ b/web/plugins/gatsby-transformer-npm-package/gatsby-node.js @@ -1,3 +1,3 @@ // This plugin will create new nodes for any package manifests found by the filesystem plugin -exports.setFieldsOnGraphQLNodeType = require('./extend-node-type'); -exports.onCreateNode = require('./on-create-node'); +exports.setFieldsOnGraphQLNodeType = require('./extend-node-type') +exports.onCreateNode = require('./on-create-node') diff --git a/web/plugins/gatsby-transformer-npm-package/on-create-node.js b/web/plugins/gatsby-transformer-npm-package/on-create-node.js index 9732f99a1..61b0d0f14 100644 --- a/web/plugins/gatsby-transformer-npm-package/on-create-node.js +++ b/web/plugins/gatsby-transformer-npm-package/on-create-node.js @@ -1,12 +1,12 @@ -const crypto = require('crypto'); +const crypto = require('crypto') -const hash = (string) => crypto.createHash('md5').update(string).digest('hex'); +const hash = (string) => crypto.createHash('md5').update(string).digest('hex') module.exports = ({ node, actions }) => { - const { createNode, createParentChildLink } = actions; + const { createNode, createParentChildLink } = actions if (node.internal.type === 'File' && node.base === 'package.json') { - const json = require(node.absolutePath); + const json = require(node.absolutePath) // Assemble node information const npmPackageNode = { @@ -21,12 +21,12 @@ module.exports = ({ node, actions }) => { private: Boolean(json.private), // Mimic remark transformer fileAbsolutePath: node.absolutePath - }; + } // Append unique node hash - npmPackageNode.internal.contentDigest = hash(JSON.stringify(npmPackageNode)); + npmPackageNode.internal.contentDigest = hash(JSON.stringify(npmPackageNode)) - createNode(npmPackageNode); - createParentChildLink({ parent: node, child: npmPackageNode }); + createNode(npmPackageNode) + createParentChildLink({ parent: node, child: npmPackageNode }) } -}; +} diff --git a/web/src/components/footer/index.jsx b/web/src/components/footer/index.jsx index ec551bb5e..7a42aa82c 100644 --- a/web/src/components/footer/index.jsx +++ b/web/src/components/footer/index.jsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React from 'react' const linkProps = { rel: 'noopener noreferrer', target: '_blank' -}; +} export default () => ( <footer className="site-footer" role="contentinfo"> @@ -31,9 +31,8 @@ export default () => ( </div> <p className="site-footer__small-print"> <small> - © THE FINANCIAL TIMES LTD 2018. FT and 'Financial Times' are trademarks of The Financial - Times Ltd + © THE FINANCIAL TIMES LTD 2018. FT and 'Financial Times' are trademarks of The Financial Times Ltd </small> </p> </footer> -); +) diff --git a/web/src/components/header/index.jsx b/web/src/components/header/index.jsx index 80cbb503f..c9e6bac59 100644 --- a/web/src/components/header/index.jsx +++ b/web/src/components/header/index.jsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { Link, withPrefix } from 'gatsby'; +import React from 'react' +import { Link, withPrefix } from 'gatsby' export default ({ showLogo }) => ( <header className="site-header"> @@ -8,9 +8,15 @@ export default ({ showLogo }) => ( </div> {showLogo ? <img className="site-header__logo" src={withPrefix('/logo.png')} alt="" /> : null} <nav role="navigation" className="site-header__menu"> - <Link to="/docs" activeClassName="is-active">Docs</Link> - <Link to="/components" activeClassName="is-active">Components</Link> - <Link to="/packages" activeClassName="is-active">Packages</Link> + <Link to="/docs" activeClassName="is-active"> + Docs + </Link> + <Link to="/components" activeClassName="is-active"> + Components + </Link> + <Link to="/packages" activeClassName="is-active"> + Packages + </Link> </nav> </header> -); +) diff --git a/web/src/components/icon/index.jsx b/web/src/components/icon/index.jsx index 410b79d98..8fe7f03ca 100644 --- a/web/src/components/icon/index.jsx +++ b/web/src/components/icon/index.jsx @@ -1,10 +1,10 @@ -import React from 'react'; +import React from 'react' -const slate = '#262A33'; +const slate = '#262A33' -const domain = 'https://www.ft.com/__origami/service/image/v2/images/raw/fticon-v1'; +const domain = 'https://www.ft.com/__origami/service/image/v2/images/raw/fticon-v1' export default ({ icon, tint = slate, ...props }) => { - const iconUrl = `${domain}:${icon}?tint=${encodeURIComponent(tint)}&source=x-dash`; + const iconUrl = `${domain}:${icon}?tint=${encodeURIComponent(tint)}&source=x-dash` return <img src={iconUrl} alt={icon} {...props} /> -}; +} diff --git a/web/src/components/layouts/basic.jsx b/web/src/components/layouts/basic.jsx index 3524e4dcb..ed0ccc0d2 100644 --- a/web/src/components/layouts/basic.jsx +++ b/web/src/components/layouts/basic.jsx @@ -1,7 +1,7 @@ -import React from 'react'; -import Helmet from 'react-helmet'; -import Header from '../header'; -import Footer from '../footer'; +import React from 'react' +import Helmet from 'react-helmet' +import Header from '../header' +import Footer from '../footer' export default ({ title, children, sidebar }) => ( <div className="basic-layout"> @@ -9,14 +9,10 @@ export default ({ title, children, sidebar }) => ( <div className="basic-layout__header"> <Header showLogo={true} /> </div> - <div className="basic-layout__content"> - {children} - </div> - <div className="basic-layout__sidebar"> - {sidebar} - </div> + <div className="basic-layout__content">{children}</div> + <div className="basic-layout__sidebar">{sidebar}</div> <div className="basic-layout__footer"> <Footer /> </div> </div> -); +) diff --git a/web/src/components/layouts/splash.jsx b/web/src/components/layouts/splash.jsx index 808f9beae..05a81bc9e 100644 --- a/web/src/components/layouts/splash.jsx +++ b/web/src/components/layouts/splash.jsx @@ -1,7 +1,7 @@ -import React from 'react'; -import Helmet from 'react-helmet'; -import Header from '../header'; -import Footer from '../footer'; +import React from 'react' +import Helmet from 'react-helmet' +import Header from '../header' +import Footer from '../footer' export default ({ title, children }) => ( <div className="splash-layout"> @@ -16,4 +16,4 @@ export default ({ title, children }) => ( <Footer /> </div> </div> -); +) diff --git a/web/src/components/module-list/index.jsx b/web/src/components/module-list/index.jsx index 44d79e027..6e38789aa 100644 --- a/web/src/components/module-list/index.jsx +++ b/web/src/components/module-list/index.jsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { Link } from 'gatsby'; +import React from 'react' +import { Link } from 'gatsby' export default ({ items }) => ( <ul className="module-list"> @@ -12,4 +12,4 @@ export default ({ items }) => ( </li> ))} </ul> -); +) diff --git a/web/src/components/sidebar/module-menu.jsx b/web/src/components/sidebar/module-menu.jsx index ba1924858..c53c15656 100644 --- a/web/src/components/sidebar/module-menu.jsx +++ b/web/src/components/sidebar/module-menu.jsx @@ -1,12 +1,10 @@ -import React from 'react'; -import { Link } from 'gatsby'; +import React from 'react' +import { Link } from 'gatsby' export default ({ heading, items }) => ( <div className="site-sidebar"> <ul className="site-sidebar__list site-sidebar__list--sticky"> - <li className="site-sidebar__item site-sidebar__item--heading"> - {heading} - </li> + <li className="site-sidebar__item site-sidebar__item--heading">{heading}</li> {items.map(({ node }, i) => ( <li key={`module-menu-${i}`} className="site-sidebar__item"> <Link to={node.path} exact activeClassName="is-active"> @@ -16,4 +14,4 @@ export default ({ heading, items }) => ( ))} </ul> </div> -); +) diff --git a/web/src/components/sidebar/pages-menu.jsx b/web/src/components/sidebar/pages-menu.jsx index cb8b78481..c093d81b9 100644 --- a/web/src/components/sidebar/pages-menu.jsx +++ b/web/src/components/sidebar/pages-menu.jsx @@ -1,11 +1,9 @@ -import React from 'react'; -import { Link } from 'gatsby'; +import React from 'react' +import { Link } from 'gatsby' const Group = ({ heading, items }) => ( <> - <li className="site-sidebar__item site-sidebar__item--heading"> - {heading} - </li> + <li className="site-sidebar__item site-sidebar__item--heading">{heading}</li> {items.map((item, i) => ( <li key={`link-${i}`} className="site-sidebar__item"> <Link to={item.link} exact activeClassName="is-active"> @@ -14,7 +12,7 @@ const Group = ({ heading, items }) => ( </li> ))} </> -); +) export default ({ data }) => ( <div className="site-sidebar"> @@ -24,4 +22,4 @@ export default ({ data }) => ( ))} </ul> </div> -); +) diff --git a/web/src/components/story-viewer/index.jsx b/web/src/components/story-viewer/index.jsx index ab8044b92..bc647943b 100644 --- a/web/src/components/story-viewer/index.jsx +++ b/web/src/components/story-viewer/index.jsx @@ -1,10 +1,10 @@ -import React from 'react'; -import { withPrefix } from 'gatsby'; +import React from 'react' +import { withPrefix } from 'gatsby' const StoryViewer = ({ name }) => { - const queryString = `?path=/story/${name}--*`; - const iframeUrl = withPrefix(`/storybook/iframe.html${queryString}`); - const linkUrl = withPrefix(`/storybook/index.html${queryString}`); + const queryString = `?path=/story/${name}--*` + const iframeUrl = withPrefix(`/storybook/iframe.html${queryString}`) + const linkUrl = withPrefix(`/storybook/index.html${queryString}`) return ( <div id="component-demos" className="story-viewer"> @@ -19,4 +19,4 @@ const StoryViewer = ({ name }) => { ) } -export default StoryViewer; +export default StoryViewer diff --git a/web/src/components/tertiary/links.jsx b/web/src/components/tertiary/links.jsx index f7f2c6891..80cde5d33 100644 --- a/web/src/components/tertiary/links.jsx +++ b/web/src/components/tertiary/links.jsx @@ -1,10 +1,10 @@ -import React from 'react'; -import { Link } from 'gatsby'; +import React from 'react' +import { Link } from 'gatsby' const linkProps = { rel: 'noopener noreferrer', target: '_blank' -}; +} export default ({ name, manifest, storybook }) => ( <div className="tertiary-menu"> @@ -29,4 +29,4 @@ export default ({ name, manifest, storybook }) => ( ) : null} </ul> </div> -); +) diff --git a/web/src/components/tertiary/subheadings.jsx b/web/src/components/tertiary/subheadings.jsx index fa7d402e7..34c05db36 100644 --- a/web/src/components/tertiary/subheadings.jsx +++ b/web/src/components/tertiary/subheadings.jsx @@ -1,33 +1,33 @@ -import React from 'react'; -import GithubSlugger from 'github-slugger'; +import React from 'react' +import GithubSlugger from 'github-slugger' const createHash = (name) => { // This is the same module as used by gatsby-remark-autolink-headers - const slugger = new GithubSlugger(); + const slugger = new GithubSlugger() // This module checks if it is duplicating created URLs, like this: // slugger.slug('url one') // url-one // slugger.slug('url one') // url-one-2 // Therefore we need to create a new class instance every time the function is applied - return '#' + slugger.slug(name); -}; + return '#' + slugger.slug(name) +} const scrollOnClick = (e) => { - e.preventDefault(); + e.preventDefault() - const target = document.querySelector(e.currentTarget.hash); + const target = document.querySelector(e.currentTarget.hash) target && target.scrollIntoView({ behavior: 'smooth' - }); -}; + }) +} export default ({ items, demos = false, minDepth = 2, maxDepth = 3 }) => { - const headings = items.filter((item) => item.depth >= minDepth && item.depth <= maxDepth); + const headings = items.filter((item) => item.depth >= minDepth && item.depth <= maxDepth) if (headings.length === 0) { // You must explicitly return null for empty nodes - return null; + return null } return ( @@ -46,10 +46,12 @@ export default ({ items, demos = false, minDepth = 2, maxDepth = 3 }) => { ))} {demos ? ( <li className="tertiary-menu__item"> - <a href="#component-demos" onClick={scrollOnClick}>Component demos</a> + <a href="#component-demos" onClick={scrollOnClick}> + Component demos + </a> </li> ) : null} </ul> </div> - ); -}; + ) +} diff --git a/web/src/html.jsx b/web/src/html.jsx index f28220fca..910659079 100644 --- a/web/src/html.jsx +++ b/web/src/html.jsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { withPrefix } from 'gatsby'; +import React from 'react' +import { withPrefix } from 'gatsby' export default class HTML extends React.Component { render() { @@ -9,10 +9,7 @@ export default class HTML extends React.Component { {this.props.headComponents} <meta charSet="utf-8" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> - <meta - name="viewport" - content="width=device-width, initial-scale=1.0, viewport-fit=cover" - /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> <script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:400,700" /> <link rel="stylesheet" href={withPrefix('/main.css')} /> @@ -24,6 +21,6 @@ export default class HTML extends React.Component { {this.props.postBodyComponents} </body> </html> - ); + ) } } diff --git a/web/src/lib/create-documentation-pages.js b/web/src/lib/create-documentation-pages.js index dfccb1c64..523364419 100644 --- a/web/src/lib/create-documentation-pages.js +++ b/web/src/lib/create-documentation-pages.js @@ -1,18 +1,18 @@ -const path = require('path'); -const Case = require('case'); +const path = require('path') +const Case = require('case') const findPageTitle = (node) => { if (node.frontmatter.title) { - return node.frontmatter.title; + return node.frontmatter.title } if (node.headings.some((heading) => heading.depth === 1)) { - return node.headings.find((heading) => heading.depth === 1).value; + return node.headings.find((heading) => heading.depth === 1).value } // HACK: use the file name as a last resort - return Case.title(path.basename(node.fields.slug)); -}; + return Case.title(path.basename(node.fields.slug)) +} module.exports = async (actions, graphql) => { const result = await graphql(` @@ -36,10 +36,10 @@ module.exports = async (actions, graphql) => { } } } - `); + `) if (result.errors) { - throw result.errors; + throw result.errors } result.data.allMarkdownRemark.edges.map(({ node }) => { @@ -52,6 +52,6 @@ module.exports = async (actions, graphql) => { source: node.fields.source, title: findPageTitle(node) } - }); - }); -}; + }) + }) +} diff --git a/web/src/lib/create-npm-package-pages.js b/web/src/lib/create-npm-package-pages.js index a2bfe926b..9c97fde17 100644 --- a/web/src/lib/create-npm-package-pages.js +++ b/web/src/lib/create-npm-package-pages.js @@ -1,5 +1,5 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require('fs') +const path = require('path') module.exports = async (actions, graphql) => { const result = await graphql(` @@ -18,25 +18,25 @@ module.exports = async (actions, graphql) => { } } } - `); + `) if (result.errors) { - throw result.errors; + throw result.errors } function hasStorybookConfig(relPath) { - const locations = ['stories/index.js', 'storybook/index.js', 'storybook/index.jsx']; + const locations = ['stories/index.js', 'storybook/index.js', 'storybook/index.jsx'] return locations.some((location) => { - const filePath = path.join(relPath, location); - return fs.existsSync(filePath); - }); + const filePath = path.join(relPath, location) + return fs.existsSync(filePath) + }) } result.data.npmPackages.edges.map(({ node }) => { // Package manifest slug will be /package so remove it - const pagePath = path.dirname(node.fields.slug); - const relPath = path.dirname(node.fileAbsolutePath); + const pagePath = path.dirname(node.fields.slug) + const relPath = path.dirname(node.fileAbsolutePath) actions.createPage({ component: path.resolve('src/templates/npm-package.jsx'), @@ -55,6 +55,6 @@ module.exports = async (actions, graphql) => { packagePath: path.join(pagePath, 'package'), readmePath: path.join(pagePath, 'readme') } - }); - }); -}; + }) + }) +} diff --git a/web/src/lib/decorate-nodes.js b/web/src/lib/decorate-nodes.js index a677c511d..83c71c4cb 100644 --- a/web/src/lib/decorate-nodes.js +++ b/web/src/lib/decorate-nodes.js @@ -1,20 +1,20 @@ -const path = require('path'); +const path = require('path') -const nodeTypesToSlug = new Set(['MarkdownRemark', 'NpmPackage']); +const nodeTypesToSlug = new Set(['MarkdownRemark', 'NpmPackage']) -const repoRoot = path.resolve('../'); +const repoRoot = path.resolve('../') const createSlug = (file) => { - const pathFromRoot = path.relative(repoRoot, file.absolutePath); - const { dir, name } = path.parse(pathFromRoot); + const pathFromRoot = path.relative(repoRoot, file.absolutePath) + const { dir, name } = path.parse(pathFromRoot) // If the file is an index file then use the parent directory name - return path.join(dir, name === 'index' ? '' : name).toLowerCase(); -}; + return path.join(dir, name === 'index' ? '' : name).toLowerCase() +} module.exports = (node, actions, getNode) => { if (nodeTypesToSlug.has(node.internal.type)) { - const file = getNode(node.parent); + const file = getNode(node.parent) // Group files by source type (currently: docs, components, packages) // "Source" meaning the name of the filesystem plugin instance @@ -22,12 +22,12 @@ module.exports = (node, actions, getNode) => { node, name: 'source', value: file.sourceInstanceName - }); + }) actions.createNodeField({ node, name: 'slug', value: '/' + createSlug(file) - }); + }) } -}; +} diff --git a/web/src/pages/components.jsx b/web/src/pages/components.jsx index 1572dfb49..7218467ca 100644 --- a/web/src/pages/components.jsx +++ b/web/src/pages/components.jsx @@ -1,14 +1,12 @@ -import React from 'react'; -import { graphql } from 'gatsby'; -import Layout from '../components/layouts/basic'; -import Sidebar from '../components/sidebar/module-menu'; -import ModuleList from '../components/module-list'; +import React from 'react' +import { graphql } from 'gatsby' +import Layout from '../components/layouts/basic' +import Sidebar from '../components/sidebar/module-menu' +import ModuleList from '../components/module-list' export const query = graphql` query { - modules: allSitePage( - filter: { context: { type: { eq: "npm-package-components" } } } - ) { + modules: allSitePage(filter: { context: { type: { eq: "npm-package-components" } } }) { edges { node { path @@ -21,7 +19,7 @@ export const query = graphql` } } } -`; +` export default ({ data }) => ( <Layout title="Components" sidebar={<Sidebar heading="Components" items={data.modules.edges} />}> @@ -30,4 +28,4 @@ export default ({ data }) => ( <ModuleList items={data.modules.edges} /> </main> </Layout> -); +) diff --git a/web/src/pages/index.jsx b/web/src/pages/index.jsx index e7c4cfd3a..e1e3a47ee 100644 --- a/web/src/pages/index.jsx +++ b/web/src/pages/index.jsx @@ -1,8 +1,8 @@ -import React from 'react'; -import { Link } from 'gatsby'; -import Icon from '../components/icon'; -import Layout from '../components/layouts/splash'; -import XLogo from '@financial-times/x-logo'; +import React from 'react' +import { Link } from 'gatsby' +import Icon from '../components/icon' +import Layout from '../components/layouts/splash' +import XLogo from '@financial-times/x-logo' export default () => ( <Layout title="Welcome"> @@ -15,7 +15,9 @@ export default () => ( <div className="hero__content"> <h1 className="hero__heading">x-dash</h1> <p className="hero__description">Shared front-end for FT.com and The App.</p> - <Link to="/docs" className="button button--inverse">Get started</Link> + <Link to="/docs" className="button button--inverse"> + Get started + </Link> </div> </div> </div> @@ -27,8 +29,7 @@ export default () => ( <ul className="intro__list"> <li className="intro__item"> <Icon className="intro__icon" icon="newspaper" /> - No more copy-and-pasting templates. Import components with well-defined, explorable - use-cases. + No more copy-and-pasting templates. Import components with well-defined, explorable use-cases. </li> <li className="intro__item"> <Icon className="intro__icon" icon="link" /> @@ -36,8 +37,8 @@ export default () => ( </li> <li className="intro__item"> <Icon className="intro__icon" icon="list" /> - Components are logic-less, with denormalised data stored in Elasticsearch, so apps are - faster and simpler. + Components are logic-less, with denormalised data stored in Elasticsearch, so apps are faster + and simpler. </li> </ul> </div> @@ -46,8 +47,8 @@ export default () => ( <ul className="intro__list"> <li className="intro__item"> <Icon className="intro__icon" icon="video" /> - Live-editable preview of every component without the headache of setting up a - development server. + Live-editable preview of every component without the headache of setting up a development + server. </li> <li className="intro__item"> <Icon className="intro__icon" icon="users" /> @@ -55,12 +56,11 @@ export default () => ( </li> <li className="intro__item"> <Icon className="intro__icon" icon="download" /> - Get set up for development quickly. Components and build tools live in a unified - monorepo. + Get set up for development quickly. Components and build tools live in a unified monorepo. </li> </ul> </div> </div> </div> </Layout> -); +) diff --git a/web/src/pages/packages.jsx b/web/src/pages/packages.jsx index 9fb63d4f8..9b105c955 100644 --- a/web/src/pages/packages.jsx +++ b/web/src/pages/packages.jsx @@ -1,14 +1,12 @@ -import React from 'react'; -import { graphql } from 'gatsby'; -import Layout from '../components/layouts/basic'; -import Sidebar from '../components/sidebar/module-menu'; -import ModuleList from '../components/module-list'; +import React from 'react' +import { graphql } from 'gatsby' +import Layout from '../components/layouts/basic' +import Sidebar from '../components/sidebar/module-menu' +import ModuleList from '../components/module-list' export const query = graphql` query { - modules: allSitePage( - filter: { context: { type: { eq: "npm-package-packages" } } } - ) { + modules: allSitePage(filter: { context: { type: { eq: "npm-package-packages" } } }) { edges { node { path @@ -21,7 +19,7 @@ export const query = graphql` } } } -`; +` export default ({ data }) => ( <Layout title="Packages" sidebar={<Sidebar heading="Packages" items={data.modules.edges} />}> @@ -30,4 +28,4 @@ export default ({ data }) => ( <ModuleList items={data.modules.edges} /> </main> </Layout> -); +) diff --git a/web/src/templates/documentation-page.jsx b/web/src/templates/documentation-page.jsx index b1c179674..3d5632ebe 100644 --- a/web/src/templates/documentation-page.jsx +++ b/web/src/templates/documentation-page.jsx @@ -1,8 +1,8 @@ -import React from 'react'; -import { graphql } from 'gatsby'; -import Layout from '../components/layouts/basic'; -import Sidebar from '../components/sidebar/pages-menu'; -import Subheadings from '../components/tertiary/subheadings'; +import React from 'react' +import { graphql } from 'gatsby' +import Layout from '../components/layouts/basic' +import Sidebar from '../components/sidebar/pages-menu' +import Subheadings from '../components/tertiary/subheadings' export default ({ pageContext, data }) => ( <Layout title={pageContext.title} sidebar={<Sidebar data={data.menu.edges} />}> @@ -19,7 +19,7 @@ export default ({ pageContext, data }) => ( </div> </div> </Layout> -); +) export const pageQuery = graphql` query($path: String!) { @@ -42,4 +42,4 @@ export const pageQuery = graphql` } } } -`; +` diff --git a/web/src/templates/npm-package.jsx b/web/src/templates/npm-package.jsx index a4d8b701f..a513ed4fd 100644 --- a/web/src/templates/npm-package.jsx +++ b/web/src/templates/npm-package.jsx @@ -1,20 +1,16 @@ -import React from 'react'; -import { graphql } from 'gatsby'; -import Layout from '../components/layouts/basic'; -import Sidebar from '../components/sidebar/module-menu'; -import Subheadings from '../components/tertiary/subheadings'; -import Links from '../components/tertiary/links'; -import StoryViewer from '../components/story-viewer'; +import React from 'react' +import { graphql } from 'gatsby' +import Layout from '../components/layouts/basic' +import Sidebar from '../components/sidebar/module-menu' +import Subheadings from '../components/tertiary/subheadings' +import Links from '../components/tertiary/links' +import StoryViewer from '../components/story-viewer' export default ({ pageContext, data, location }) => ( <Layout title={pageContext.title} sidebar={ - <Sidebar - heading={pageContext.source} - items={data.modules.edges} - location={location.pathname} - /> + <Sidebar heading={pageContext.source} items={data.modules.edges} location={location.pathname} /> }> <div className="content-layout"> <main className="content-layout__main" role="main"> @@ -25,17 +21,13 @@ export default ({ pageContext, data, location }) => ( </main> <div className="content-layout__tertiary"> <div className="content-layout__tertiary-inner"> - <Links - name={pageContext.title} - manifest={data.npm.manifest} - storybook={pageContext.storybook} - /> + <Links name={pageContext.title} manifest={data.npm.manifest} storybook={pageContext.storybook} /> <Subheadings items={data.markdown.headings} demos={pageContext.storybook} /> </div> </div> </div> </Layout> -); +) export const pageQuery = graphql` query($type: String!, $packagePath: String!, $readmePath: String!) { @@ -60,4 +52,4 @@ export const pageQuery = graphql` } } } -`; +` From ea99375a476c4df07acebccdcbdaee2ddab35579 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Tue, 3 Nov 2020 14:04:44 +0000 Subject: [PATCH 588/760] Update snapshot test for components changed by prettier --- .../__snapshots__/snapshots.test.js.snap | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap index ac9948829..ae270cfd9 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -161,12 +161,14 @@ exports[`@financial-times/x-gift-article renders a default With a bad response f <div className="GiftArticle_message__3UjVE" > - You have + You have + <strong> gift article credits </strong> - left this month + + left this month </div> <div className="GiftArticle_buttons__1zOQK" @@ -278,12 +280,14 @@ exports[`@financial-times/x-gift-article renders a default With gift credits x-g <div className="GiftArticle_message__3UjVE" > - You have + You have + <strong> gift article credits </strong> - left this month + + left this month </div> <div className="GiftArticle_buttons__1zOQK" @@ -686,12 +690,14 @@ exports[`@financial-times/x-gift-article renders a default With native share on <div className="GiftArticle_message__3UjVE" > - You have + You have + <strong> gift article credits </strong> - left this month + + left this month </div> <div className="GiftArticle_buttons__1zOQK" @@ -803,12 +809,14 @@ exports[`@financial-times/x-gift-article renders a default Without gift credits <div className="GiftArticle_message__3UjVE" > - You have + You have + <strong> gift article credits </strong> - left this month + + left this month </div> <div className="GiftArticle_buttons__1zOQK" From 60ec53257d291538b2c60610a9632c586bac880b Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Fri, 6 Nov 2020 15:04:42 +0000 Subject: [PATCH 589/760] circelci config to wait on approval on renovate and nori --- .circleci/config.yml | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 68a822a92..51fea614b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,12 +57,16 @@ references: # # Filters # - filters_branch_build: &filters_branch_build + + filters_only_renovate_nori: &filters_only_renovate_nori branches: - ignore: - - gh-pages + only: /(^renovate-.*|^nori\/.*)/ + + filters_ignore_tags_renovate_nori_build: &filters_ignore_tags_renovate_nori_build tags: ignore: /.*/ + branches: + ignore: /(^renovate-.*|^nori\/.*|^gh-pages)/ filters_release_build: &filters_release_build tags: @@ -78,7 +82,7 @@ references: branches: ignore: /.*/ - filters_master_branch: &filters_master_branch + filters_only_master: &filters_only_master branches: only: - master @@ -180,18 +184,29 @@ workflows: jobs: - build: filters: - <<: *filters_branch_build + <<: *filters_ignore_tags_renovate_nori_build - test: - filters: - <<: *filters_branch_build requires: - build - deploy: filters: - <<: *filters_master_branch + <<: *filters_only_master requires: - test + renovate-nori-build-test: + jobs: + - waiting-for-approval: + type: approval + filters: + <<: *filters_only_renovate_nori + - build: + requires: + - waiting-for-approval + - test: + requires: + - build + build-test-publish-deploy: jobs: - build: From dfa98acc07dbf1d10c1aace6ab8993eef792c5e8 Mon Sep 17 00:00:00 2001 From: andygout <andygout@hotmail.co.uk> Date: Wed, 28 Oct 2020 12:29:11 +0000 Subject: [PATCH 590/760] Update to Node v12 --- .circleci/config.yml | 2 +- components/x-gift-article/readme.md | 2 +- components/x-interaction/package.json | 2 +- components/x-interaction/readme.md | 2 +- components/x-live-blog-post/package.json | 2 +- components/x-live-blog-post/readme.md | 4 ++-- components/x-live-blog-wrapper/package.json | 2 +- components/x-live-blog-wrapper/readme.md | 24 ++++++++++----------- components/x-podcast-launchers/package.json | 2 +- components/x-podcast-launchers/readme.md | 2 +- components/x-privacy-manager/package.json | 2 +- components/x-privacy-manager/readme.md | 2 +- components/x-styling-demo/package.json | 2 +- components/x-teaser-timeline/package.json | 2 +- components/x-teaser-timeline/readme.md | 10 ++++----- components/x-teaser/package.json | 2 +- components/x-teaser/readme.md | 4 ++-- docs/get-started/installation.md | 2 +- packages/x-engine/package.json | 2 +- packages/x-engine/readme.md | 2 +- packages/x-handlebars/package.json | 2 +- packages/x-handlebars/readme.md | 2 +- packages/x-node-jsx/package.json | 2 +- packages/x-node-jsx/readme.md | 2 +- packages/x-rollup/src/rollup-config.js | 4 ++-- private/blueprints/component/package.json | 2 +- private/blueprints/component/readme.md | 2 +- 27 files changed, 45 insertions(+), 45 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 51fea614b..81286d476 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ references: container_config_node: &container_config_node working_directory: ~/project/build docker: - - image: circleci/node:10.13 + - image: circleci/node:12 workspace_root: &workspace_root ~/project diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 6c5d78c1e..dc22805a8 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -4,7 +4,7 @@ This module provides a gift article form. ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/x-gift-article diff --git a/components/x-interaction/package.json b/components/x-interaction/package.json index 33dbf40dc..896651938 100644 --- a/components/x-interaction/package.json +++ b/components/x-interaction/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-interaction", "engines": { - "node": ">= 6.0.0" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/components/x-interaction/readme.md b/components/x-interaction/readme.md index 1e2bafac8..94dcfe56c 100644 --- a/components/x-interaction/readme.md +++ b/components/x-interaction/readme.md @@ -4,7 +4,7 @@ This module enables you to write x-dash components that respond to events and ch ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/x-interaction diff --git a/components/x-live-blog-post/package.json b/components/x-live-blog-post/package.json index f6d0f2058..a5911e06f 100644 --- a/components/x-live-blog-post/package.json +++ b/components/x-live-blog-post/package.json @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-live-blog-post", "engines": { - "node": ">= 10.x" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index c2c8719d4..181febec5 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -1,11 +1,11 @@ # x-live-blog-post -This module displays a live blog post with title, body, timestamp and share buttons. +This module displays a live blog post with title, body, timestamp and share buttons. ## Installation -This module is compatible with Node 10.x+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/x-live-blog-post diff --git a/components/x-live-blog-wrapper/package.json b/components/x-live-blog-wrapper/package.json index e57c25f5f..e2dbca31e 100644 --- a/components/x-live-blog-wrapper/package.json +++ b/components/x-live-blog-wrapper/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-live-blog-wrapper", "engines": { - "node": ">= 10.x" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index ebad8ebe0..b34696c93 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -1,11 +1,11 @@ # x-live-blog-wrapper -This module displays a list of live blog posts using `x-live-blog-post` component. It also connects to an event stream which provides updates for the list. Based on these update events this component will add, remove and update `x-live-blog-post` components in the list. +This module displays a list of live blog posts using `x-live-blog-post` component. It also connects to an event stream which provides updates for the list. Based on these update events this component will add, remove and update `x-live-blog-post` components in the list. ## Installation -This module is compatible with Node 10+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/x-live-blog-wrapper @@ -58,7 +58,7 @@ When rendering this component at the server side, hydration data must be rendere To successfully hydrate this component at the client side, the `id` property **must** be provided when rendering it at the server side. `x-interaction` will add this id to the markup as a `data-x-dash-id` attribute. This property can later be used to identify the markup. The consuming app needs to ensure that the `id` is unique. - + ```jsx import { Serialiser, HydrationData } from '@financial-times/x-interaction'; import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; @@ -75,7 +75,7 @@ const serialiser = new Serialiser(); To hydrate this component at the client side, use `hydrate()` function provided by `x-interaction`. -```js +```js import { hydrate } from '@financial-times/x-interaction'; hydrate(); @@ -84,7 +84,7 @@ hydrate(); ### Live updates This component exports a function named `listenToLiveBlogEvents` which is used for listening to client side live blog updates. These updates come in the form of server sent events sent by `next-live-event-api`. -This function is used in slightly different ways when rendering the component at the client side vs rendering it at the server side. +This function is used in slightly different ways when rendering the component at the client side vs rendering it at the server side. #### Client side rendering A reference to the actions object should be passed as an argument when calling this function for a client side rendered component. @@ -93,7 +93,7 @@ import { LiveBlogWrapper, listenToLiveBlogEvents } from '@financial-times/x-live const actionsRef = actions => { listenToLiveBlogEvents({ - liveBlogWrapperElementId: 'live-blog-wrapper', + liveBlogWrapperElementId: 'live-blog-wrapper', liveBlogPackageUuid: 'package-uuid', actions // for client side rendered component only }); @@ -132,7 +132,7 @@ import { listenToLiveBlogEvents } from '@financial-times/x-live-blog-wrapper'; hydrate(); listenToLiveBlogEvents({ - liveBlogWrapperElementId: 'live-blog-wrapper', + liveBlogWrapperElementId: 'live-blog-wrapper', liveBlogPackageUuid: 'package-uuid' }); ``` @@ -156,7 +156,7 @@ const wrapperElement = document.querySelector( `[data-live-blog-wrapper-id="x-dash-element-id"]` ); -wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', +wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', (ev) => { const { post } = ev.detail; @@ -165,7 +165,7 @@ wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', // LiveBlogPost element }); -wrapperElement.addEventListener('LiveBlogWrapper.DELETE_POST', +wrapperElement.addEventListener('LiveBlogWrapper.DELETE_POST', (ev) => { const { postId } = ev.detail; @@ -173,10 +173,10 @@ wrapperElement.addEventListener('LiveBlogWrapper.DELETE_POST', // LiveBlogPost element }); -wrapperElement.addEventListener('LiveBlogWrapper.UPDATE_POST', +wrapperElement.addEventListener('LiveBlogWrapper.UPDATE_POST', (ev) => { const { post } = ev.detail; - + // post object contains data about a live blog post // post.postId can be used to identify the newly rendered // LiveBlogPost element @@ -189,4 +189,4 @@ Feature | Type | Notes `articleUrl` | String | URL of the live blog - used for sharing `showShareButtons` | Boolean | if `true` displays social media sharing buttons in posts `posts` | Array | Array of live blog post data -`id` | String | **(required)** Unique id used for identifying the element in the document. +`id` | String | **(required)** Unique id used for identifying the element in the document. diff --git a/components/x-podcast-launchers/package.json b/components/x-podcast-launchers/package.json index 92cb27107..b7ddc4f35 100644 --- a/components/x-podcast-launchers/package.json +++ b/components/x-podcast-launchers/package.json @@ -33,7 +33,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-podcastlaunchers", "engines": { - "node": ">= 6.0.0" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/components/x-podcast-launchers/readme.md b/components/x-podcast-launchers/readme.md index 26924cb41..5e9f8f166 100644 --- a/components/x-podcast-launchers/readme.md +++ b/components/x-podcast-launchers/readme.md @@ -11,7 +11,7 @@ This component also renders a myFT follow button (x-follow-button) for the conce ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/x-podcast-launchers diff --git a/components/x-privacy-manager/package.json b/components/x-privacy-manager/package.json index c7949f956..e62242cfc 100644 --- a/components/x-privacy-manager/package.json +++ b/components/x-privacy-manager/package.json @@ -17,7 +17,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-privacy-manager", "engines": { - "node": "10.x" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/components/x-privacy-manager/readme.md b/components/x-privacy-manager/readme.md index fb465a4cc..18cc9b9d4 100644 --- a/components/x-privacy-manager/readme.md +++ b/components/x-privacy-manager/readme.md @@ -8,7 +8,7 @@ It is rendered with Page Kit on FT.com at https://www.ft.com/preferences/privacy ## Installation -This module is compatible with Node 10.x and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/x-privacy-manager diff --git a/components/x-styling-demo/package.json b/components/x-styling-demo/package.json index d1cd823c3..79fb2863e 100644 --- a/components/x-styling-demo/package.json +++ b/components/x-styling-demo/package.json @@ -23,6 +23,6 @@ "classnames": "^2.2.6" }, "engines": { - "node": ">= 6.0.0" + "node": "12.x" } } diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index 2aa3662bb..443846106 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -35,7 +35,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-teaser-timeline", "engines": { - "node": ">= 6.0.0" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md index a3495baa6..ad4d09370 100644 --- a/components/x-teaser-timeline/readme.md +++ b/components/x-teaser-timeline/readme.md @@ -5,7 +5,7 @@ It will optionally group today's articles into "latest" and "earlier" too. ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/x-teaser-timeline @@ -25,7 +25,7 @@ If selectively importing o-teaser's styles via scss, then you will need the foll $o-teaser-is-silent: true; @import 'o-teaser/main'; @include oTeaser(('default', 'images', 'timestamp'), ('small')); -``` +``` See the [x-teaser](https://github.com/Financial-Times/x-dash/tree/master/components/x-teaser) documentation. @@ -54,12 +54,12 @@ Feature | Type | Notes `items` | Array | (Mandatory) Array of objects, in Teaser format, representating content items to render. The items should be in newest-first order. `timezoneOffset` | Number | (Defaults using runtime clock) Minutes to offset item publish times in order to display in user's timezone. Negative means ahead of UTC. `localTodayDate` | String | (Defaults using runtime clock) ISO format YYYY-MM-DD representating today's date in the user's timezone. -`latestItemsTime` | String | ISO time (HH:mm:ss). If provided, will be used in combination with `localTodayDate` to render today's items into separate "Latest" and "Earlier" groups. +`latestItemsTime` | String | ISO time (HH:mm:ss). If provided, will be used in combination with `localTodayDate` to render today's items into separate "Latest" and "Earlier" groups. `showSaveButtons` | Boolean | (Default to true). Option to hide x-article-save-buttons if they are not needed. Those buttons will get their saved/unsaved state from a `saved` property of the content item. `customSlotContent` | String | Content to insert at `customSlotPosition`. `customSlotPosition` | Number | (Default is 2). Where to insert `customSlotContent`. The custom content will be inserted after the item at this position number. If this position is greater than the number items to render, then it will be inserted last. `csrfToken` | String | A CSRF token that will be used by the save buttons (if shown). -`latestItemsAgeHours`| Number | (Optional). If provided, used to calculate a cutoff time before which no article will count as "latest", regardless of the value of `latestItemsTime`. If omitted, articles before midnight this morning will not count as "latest". +`latestItemsAgeHours`| Number | (Optional). If provided, used to calculate a cutoff time before which no article will count as "latest", regardless of the value of `latestItemsTime`. If omitted, articles before midnight this morning will not count as "latest". Example: @@ -70,4 +70,4 @@ Example: localTodayDate="2018-10-30" latestItemsTime="11:52:30" /> -``` \ No newline at end of file +``` diff --git a/components/x-teaser/package.json b/components/x-teaser/package.json index 500d6c9f8..e64ccf06c 100644 --- a/components/x-teaser/package.json +++ b/components/x-teaser/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-teaser", "engines": { - "node": ">= 6.0.0" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index cb6e08ae0..30d9cc843 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -4,7 +4,7 @@ This module provides templates for use with [o-teaser](https://github.com/Financ ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/x-teaser @@ -173,7 +173,7 @@ Property | Type | Notes `image` | [media](#media-props) | `imageSize` | String | XS, Small, Medium, Large, XL or XXL `imageLazyLoad` | Boolean, String | Output image with `data-src` attribute. If this is a string it will be appended to the image as a class name. -`imageHighestQuality`| Boolean | Calls image service with "quality=highest" option, works only with XXL images +`imageHighestQuality`| Boolean | Calls image service with "quality=highest" option, works only with XXL images #### Headshot Props diff --git a/docs/get-started/installation.md b/docs/get-started/installation.md index 053bd6c2d..0f479df0f 100644 --- a/docs/get-started/installation.md +++ b/docs/get-started/installation.md @@ -7,7 +7,7 @@ To get started with x-dash, you'll need to make sure you have the following soft 1. [Git](https://git-scm.com/) 2. [Make](https://www.gnu.org/software/make/) -3. [Node.js](https://nodejs.org/en/) (version 8 or higher is required) +3. [Node.js](https://nodejs.org/en/) (version 12) 4. [npm](http://npmjs.com/) Please note that x-dash has only been tested in Mac and Linux environments. If you are on a Mac you may find it easiest to install the [Command Line Tools](https://developer.apple.com/download/more/) package which includes Git and Make. diff --git a/packages/x-engine/package.json b/packages/x-engine/package.json index 89c101950..094647549 100644 --- a/packages/x-engine/package.json +++ b/packages/x-engine/package.json @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine", "engines": { - "node": ">= 6.0.0" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/packages/x-engine/readme.md b/packages/x-engine/readme.md index 7a72843c0..514f47c86 100644 --- a/packages/x-engine/readme.md +++ b/packages/x-engine/readme.md @@ -4,7 +4,7 @@ This module is a consolidation library to render `x-dash` components with any co ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install -S @financial-times/x-engine diff --git a/packages/x-handlebars/package.json b/packages/x-handlebars/package.json index 525438701..1b191cacd 100644 --- a/packages/x-handlebars/package.json +++ b/packages/x-handlebars/package.json @@ -17,7 +17,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/packages/x-handlebars", "engines": { - "node": ">= 6.0.0" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/packages/x-handlebars/readme.md b/packages/x-handlebars/readme.md index 8f2729036..f3383c2b4 100644 --- a/packages/x-handlebars/readme.md +++ b/packages/x-handlebars/readme.md @@ -4,7 +4,7 @@ This module provides Handlebars helper functions to render `x-dash` component pa ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install -S @financial-times/x-handlebars diff --git a/packages/x-node-jsx/package.json b/packages/x-node-jsx/package.json index 3a2a2c1dc..b81ad34f2 100644 --- a/packages/x-node-jsx/package.json +++ b/packages/x-node-jsx/package.json @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/packages/x-node-jsx", "engines": { - "node": ">= 8.0.0" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/packages/x-node-jsx/readme.md b/packages/x-node-jsx/readme.md index 9dd465654..2d1604363 100644 --- a/packages/x-node-jsx/readme.md +++ b/packages/x-node-jsx/readme.md @@ -8,7 +8,7 @@ This module extends Node's `require()` function to enable the use of `.jsx` file ## Installation -This module is compatible with Node 8+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install -S @financial-times/x-node-jsx diff --git a/packages/x-rollup/src/rollup-config.js b/packages/x-rollup/src/rollup-config.js index 2ce61e61f..41cf9a6f8 100644 --- a/packages/x-rollup/src/rollup-config.js +++ b/packages/x-rollup/src/rollup-config.js @@ -28,7 +28,7 @@ module.exports = ({ input, pkg }) => { plugins: [ babel( babelConfig({ - targets: [{ node: 6 }] + targets: [{ node: 12 }] }) ), ...plugins @@ -46,7 +46,7 @@ module.exports = ({ input, pkg }) => { plugins: [ babel( babelConfig({ - targets: [{ node: 6 }] + targets: [{ node: 12 }] }) ), ...plugins diff --git a/private/blueprints/component/package.json b/private/blueprints/component/package.json index 9c9d08241..68d108966 100644 --- a/private/blueprints/component/package.json +++ b/private/blueprints/component/package.json @@ -24,7 +24,7 @@ }, "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/{{packageName}}", "engines": { - "node": ">= 6.0.0" + "node": "12.x" }, "publishConfig": { "access": "public" diff --git a/private/blueprints/component/readme.md b/private/blueprints/component/readme.md index ae258400a..3fe9b7a91 100644 --- a/private/blueprints/component/readme.md +++ b/private/blueprints/component/readme.md @@ -5,7 +5,7 @@ This module has these features and scope. ## Installation -This module is compatible with Node 6+ and is distributed on npm. +This module is supported on Node 12 and is distributed on npm. ```bash npm install --save @financial-times/{{packageName}} From f024765795e8e8384424761f84a77dade05ead09 Mon Sep 17 00:00:00 2001 From: andygout <andygout@hotmail.co.uk> Date: Tue, 10 Nov 2020 08:33:04 +0000 Subject: [PATCH 591/760] Add x-live-blog-post node-sass dev dependency --- components/x-live-blog-post/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/x-live-blog-post/package.json b/components/x-live-blog-post/package.json index a5911e06f..ea4e01578 100644 --- a/components/x-live-blog-post/package.json +++ b/components/x-live-blog-post/package.json @@ -22,7 +22,8 @@ "devDependencies": { "@financial-times/x-test-utils": "file:../../packages/x-test-utils", "@financial-times/x-rollup": "file:../../packages/x-rollup", - "bower": "^1.8.8" + "bower": "^1.8.8", + "node-sass": "^4.9.2" }, "repository": { "type": "git", From 47c88f998da91c9dd6bd62e39616cfb8fb464f9a Mon Sep 17 00:00:00 2001 From: andygout <andygout@hotmail.co.uk> Date: Tue, 10 Nov 2020 10:25:21 +0000 Subject: [PATCH 592/760] Bump CircleCI cache keys --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 81286d476..4fba0763d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,24 +22,24 @@ references: # cache_keys_root: &cache_keys_root keys: - - cache-root-v2-{{ .Branch }}-{{ checksum "./package.json" }} + - cache-root-v3-{{ .Branch }}-{{ checksum "./package.json" }} cache_keys_docs: &cache_keys_docs keys: - - cache-docs-v2-{{ .Branch }}-{{ checksum "./web/package.json" }} + - cache-docs-v3-{{ .Branch }}-{{ checksum "./web/package.json" }} # # Cache creation # create_cache_root: &create_cache_root save_cache: - key: cache-root-v2-{{ .Branch }}-{{ checksum "./package.json" }} + key: cache-root-v3-{{ .Branch }}-{{ checksum "./package.json" }} paths: - ./node_modules/ create_cache_docs: &create_cache_docs save_cache: - key: cache-docs-v2-{{ .Branch }}-{{ checksum "./web/package.json" }} + key: cache-docs-v3-{{ .Branch }}-{{ checksum "./web/package.json" }} paths: - ./web/node_modules/ From be96eb0e44b6b0ef70a3112716fc768b302daf36 Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Wed, 11 Nov 2020 14:44:25 +0000 Subject: [PATCH 593/760] Update how we access a live blog post ID `next-live-blog-trigger` renamed the post ID property from `id` to `postId` which means when we receive the event, we need to access the post id as `post.postId`. See here: https://github.com/Financial-Times/next-live-blogs-trigger/blob/822a8acbe0734e087b20af0cb97bd52f1d24f3dd/utils/filter-and-format-events.js#L39 --- components/x-live-blog-wrapper/src/LiveEventListener.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index 5b5bf8e4a..d6a237045 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -1,7 +1,7 @@ const parsePost = (event) => { const post = JSON.parse(event.data) - if (!post || !post.id) { + if (!post || !post.postId) { return } @@ -98,8 +98,9 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, return } - invokeAction('deletePost', [post.id]) - dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId: post.id }) + const postId = post.postId + invokeAction('deletePost', [postId]) + dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId }) }) } From c91c703eb2f620f092313a4bdadf71bda3fea81f Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Wed, 11 Nov 2020 14:57:25 +0000 Subject: [PATCH 594/760] Bubble event to parent w/ data-x-dash-id attribute `x-interaction` listens for the `x-interaction.trigger-action` event on the element with a `data-x-dash-id` attribute. Once it hears that event, it kicks off an action based on the event type (eg. insertPost, updatePost, deletePost). This wasn't working as expected because we use the `data-x-live-blog-id` element to dispatch the `x-interaction.trigger-action` event. For `x-interaction` to do it's thing, we need to bubble the event to the `data-x-dash-id` element: ``` <div data-x-dash-id="live-blog-wrapper"> <div class="x-live-blog-wrapper" data-live-blog-wrapper-id="live-blog-wrapper"> {posts rendered here} </div> </div> ``` --- components/x-live-blog-wrapper/src/LiveEventListener.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index d6a237045..83217fdf9 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -31,7 +31,9 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, // If no 'actions' argument is passed when calling listenToLiveBlogEvents // function, we assume the component is rendered at the server side and trigger // the actions using this method. - wrapper.dispatchEvent(new CustomEvent('x-interaction.trigger-action', { detail: { action, args } })) + wrapper.dispatchEvent( + new CustomEvent('x-interaction.trigger-action', { detail: { action, args }, bubbles: true }) + ) } } From 6fbbe1a552dc03770bea699083211c9e373c705f Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Thu, 12 Nov 2020 15:36:45 +0000 Subject: [PATCH 595/760] Remove Snyk command from CircleCI publish step A Snyk error is preventing me from releasing a new version of `x-dash`. We thought this error was resolved but it looks not to be the case. Andy has reported the issue to Snyk again and we are waiting on them. Because we don't know if there will be a fix soon, I am going to remove the Snyk command, publish a new release and then re-add the Snyk command. See more info here: https://financialtimes.slack.com/archives/C3TJ6KXEU/p1605111138223300 --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4fba0763d..e4f2a6aa9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,7 +124,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From 128cfb8dc388e77fb0f214d458cf4a8a960ac8a4 Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Thu, 12 Nov 2020 15:59:24 +0000 Subject: [PATCH 596/760] Re-add Snyk command Undoing this PR: https://github.com/Financial-Times/x-dash/pull/552 --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e4f2a6aa9..4fba0763d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,6 +124,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From ab0ca47444ae3de6405718c97a8a82ad5803c336 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Thu, 12 Nov 2020 17:02:56 +0000 Subject: [PATCH 597/760] Upgrade storybook package to v6 --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 0e179c17c..21f9d899d 100644 --- a/package.json +++ b/package.json @@ -25,14 +25,14 @@ "devDependencies": { "@babel/core": "^7.4.5", "@financial-times/athloi": "^1.0.0-beta.26", - "@storybook/addon-knobs": "^5.1.8", - "@storybook/addon-viewport": "^5.1.8", - "@storybook/react": "^5.1.8", + "@storybook/addon-knobs": "^6.0.28", + "@storybook/addon-viewport": "^6.0.28", + "@storybook/react": "^6.0.28", "@types/jest": "26.0.0", "@typescript-eslint/parser": "^3.0.0", "babel-loader": "^8.0.4", "copy-webpack-plugin": "^5.0.2", - "core-js": "^2.6.8", + "core-js": "^3.7.0", "eslint": "^5.16.0", "eslint-config-prettier": "^5.0.0", "eslint-plugin-jest": "^22.6.4", From db81f3ce73d20bca8e7ccbd74fe048ae1f2d690d Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Mon, 16 Nov 2020 13:55:52 +0000 Subject: [PATCH 598/760] Fix bug with accessing post ID I forgot to update how we access post ID in this commit: https://github.com/Financial-Times/x-dash/pull/551/commits/be96eb0e44b6b0ef70a3112716fc768b302daf36. Essentially, `next-live-blog-trigger` renamed the post ID property from `id` to `postId` which means when we receive the event, we need to access the post id as `updated.postId`. --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 2 +- .../x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index cc89bc24c..36790dad4 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -14,7 +14,7 @@ const withLiveBlogWrapperActions = withActions({ updatePost(updated) { return (props) => { - const index = props.posts.findIndex((post) => post.id === updated.id) + const index = props.posts.findIndex((post) => post.id === updated.postId) if (index >= 0) { props.posts[index] = updated } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 2f4be81f2..6b6e5bdda 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -72,7 +72,7 @@ describe('liveBlogWrapperActions', () => { it('updates a post', () => { const updatedPost2 = { - id: '2', + postId: '2', title: 'Updated title' } From bee1ab8436ddbfc75614fe99c9329e24e166f68a Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Mon, 16 Nov 2020 14:28:00 +0000 Subject: [PATCH 599/760] Remove failing Snyk CircleCI command A Snyk error is preventing me from releasing a new version of `x-dash`. Snyk is aware of the issue and Andy is working with them to debug. Because we don't know if there will be a fix soon, I am going to remove the Snyk command, publish a new release and then re-add the Snyk command. --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4fba0763d..e4f2a6aa9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,7 +124,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From ad911ceeabbd44f85c3ae0d249bde30b4b8ddb6e Mon Sep 17 00:00:00 2001 From: Keran Braich <30316203+ker-an@users.noreply.github.com> Date: Mon, 16 Nov 2020 15:55:01 +0000 Subject: [PATCH 600/760] Revert "Remove failing Snyk CircleCI command" --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e4f2a6aa9..4fba0763d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,6 +124,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From be50ffe4a017796531dcae6dfb4a733e7367ea5a Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Mon, 16 Nov 2020 16:18:33 +0000 Subject: [PATCH 601/760] converted buildStory to storiesOf storybook --- .storybook/build-story.js | 84 ---------- .storybook/config.js | 17 +-- .storybook/register-components.js | 12 -- .storybook/storybook.utils.js | 37 +++++ components/x-gift-article/stories/index.js | 18 --- .../{stories => storybook}/error-response.js | 0 .../{stories => storybook}/free-article.js | 0 components/x-gift-article/storybook/index.jsx | 105 +++++++++++++ .../{stories => storybook}/native-share.js | 0 .../with-gift-credits.js | 0 .../{stories => storybook}/with-gift-link.js | 0 .../without-gift-credits.js | 0 .../x-live-blog-post/storybook/index.jsx | 2 +- .../x-podcast-launchers/stories/index.js | 17 --- .../{stories => storybook}/example.js | 0 .../x-podcast-launchers/storybook/index.jsx | 36 +++++ .../{stories => storybook}/knobs.js | 0 .../x-privacy-manager/storybook/index.js | 20 --- .../x-privacy-manager/storybook/index.jsx | 79 ++++++++++ components/x-styling-demo/stories/index.js | 15 -- components/x-styling-demo/storybook/index.jsx | 36 +++++ .../{stories => storybook}/styling.js | 0 components/x-teaser-timeline/stories/index.js | 16 -- .../{stories => storybook}/content-items.json | 0 .../x-teaser-timeline/storybook/index.jsx | 34 +++++ .../{stories => storybook}/knobs.js | 0 .../{stories => storybook}/timeline.js | 0 .../x-teaser/src/concerns/image-service.js | 1 - components/x-teaser/storybook/index.js | 27 ---- components/x-teaser/storybook/index.jsx | 144 ++++++++++++++++++ 30 files changed, 478 insertions(+), 222 deletions(-) delete mode 100644 .storybook/build-story.js delete mode 100644 .storybook/register-components.js create mode 100644 .storybook/storybook.utils.js delete mode 100644 components/x-gift-article/stories/index.js rename components/x-gift-article/{stories => storybook}/error-response.js (100%) rename components/x-gift-article/{stories => storybook}/free-article.js (100%) create mode 100644 components/x-gift-article/storybook/index.jsx rename components/x-gift-article/{stories => storybook}/native-share.js (100%) rename components/x-gift-article/{stories => storybook}/with-gift-credits.js (100%) rename components/x-gift-article/{stories => storybook}/with-gift-link.js (100%) rename components/x-gift-article/{stories => storybook}/without-gift-credits.js (100%) delete mode 100644 components/x-podcast-launchers/stories/index.js rename components/x-podcast-launchers/{stories => storybook}/example.js (100%) create mode 100644 components/x-podcast-launchers/storybook/index.jsx rename components/x-podcast-launchers/{stories => storybook}/knobs.js (100%) delete mode 100644 components/x-privacy-manager/storybook/index.js create mode 100644 components/x-privacy-manager/storybook/index.jsx delete mode 100644 components/x-styling-demo/stories/index.js create mode 100644 components/x-styling-demo/storybook/index.jsx rename components/x-styling-demo/{stories => storybook}/styling.js (100%) delete mode 100644 components/x-teaser-timeline/stories/index.js rename components/x-teaser-timeline/{stories => storybook}/content-items.json (100%) create mode 100644 components/x-teaser-timeline/storybook/index.jsx rename components/x-teaser-timeline/{stories => storybook}/knobs.js (100%) rename components/x-teaser-timeline/{stories => storybook}/timeline.js (100%) delete mode 100644 components/x-teaser/storybook/index.js create mode 100644 components/x-teaser/storybook/index.jsx diff --git a/.storybook/build-story.js b/.storybook/build-story.js deleted file mode 100644 index 6b08a6357..000000000 --- a/.storybook/build-story.js +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react' -import BuildService from './build-service' -import { storiesOf } from '@storybook/react' -import * as knobsAddon from '@storybook/addon-knobs' -import { Helmet } from 'react-helmet' -import path from 'path' - -// HACK: The browser bundle for Fetch Mock implicitly depends on core-js 2.x so ensure -// that this is an explicit dependency of the repository root. -import fetchMock from 'fetch-mock' - -const defaultKnobs = () => ({}) - -/** - * Create Props - * @param {{ [key: string]: any }} defaultData - * @param {String[]} allowedKnobs - * @param {Function} hydrateKnobs - */ -function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs) { - // Inject knobs add-on into given dependency container - const knobs = hydrateKnobs(defaultData, knobsAddon) - // Mix the available knob props into default data - const mixedProps = { ...defaultData, ...knobs } - - if (allowedKnobs.length === 0) { - return mixedProps - } - - return allowedKnobs.reduce((map, prop) => { - if (mixedProps.hasOwnProperty(prop)) { - const value = mixedProps[prop] - - // Knobs are functions which need calling to register them - if (typeof value === 'function') { - map[prop] = value() - } else { - map[prop] = value - } - } - - return map - }, {}) -} - -/** - * Build Story - * @param {String} name - * @param {{ [key: string]: string }} dependencies - * @param {Function} Component - * @param {Function} knobs - * @param {{ title: String, data: {}, knobs: String[], m: module }} story - */ -function buildStory({ package: pkg, dependencies, component: Component, knobs, story }) { - const name = path.basename(pkg.name) - const storybook = storiesOf(name, story.m) - - storybook.addDecorator(knobsAddon.withKnobs) - - storybook.add(story.title, () => { - const props = createProps(story.data, story.knobs, knobs) - - if (story.fetchMock) { - fetchMock.restore() // to isolate the mocks to each story - story.fetchMock(fetchMock) - } - - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Component {...props} /> - </div> - ) - }) - - return storybook -} - -export default buildStory diff --git a/.storybook/config.js b/.storybook/config.js index 670f654f9..c0a20caa6 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -1,19 +1,14 @@ import { configure } from '@storybook/react' -import buildStory from './build-story' -import * as components from './register-components' configure(() => { - // We used to use a configuration based technique for defining stories for each component as we - // wanted to reuse the data in multiple places. We now recommend you define stories in a regular - // way as defined by the Storybook documentation: - // <https://storybook.js.org/docs/basics/writing-stories/> - components.forEach(({ stories, ...data }) => { - stories.forEach((story) => buildStory({ story, ...data })) - }) - - // Add regular story definitions (i.e. those using storiesOf() directly below) require('../components/x-increment/storybook/index.jsx') require('../components/x-follow-button/storybook/index.jsx') require('../components/x-live-blog-post/storybook/index.jsx') require('../components/x-live-blog-wrapper/storybook/index.jsx') + require('../components/x-teaser/storybook/index.jsx') + require('../components/x-styling-demo/storybook/index.jsx') + require('../components/x-gift-article/storybook/index.jsx') + require('../components/x-podcast-launchers/storybook/index.jsx') + require('../components/x-teaser-timeline/storybook/index.jsx') + require('../components/x-privacy-manager/storybook/index.jsx') }, module) diff --git a/.storybook/register-components.js b/.storybook/register-components.js deleted file mode 100644 index 0225842e8..000000000 --- a/.storybook/register-components.js +++ /dev/null @@ -1,12 +0,0 @@ -// NOTE: Only add configuration based component stories to this file. If you are using regular -// story definitions (i.e. implementing storiesOf() directly) then please add these to config.js -const components = [ - require('../components/x-teaser/storybook'), - require('../components/x-styling-demo/stories'), - require('../components/x-gift-article/stories'), - require('../components/x-podcast-launchers/stories'), - require('../components/x-teaser-timeline/stories'), - require('../components/x-privacy-manager/storybook') -] - -module.exports = components diff --git a/.storybook/storybook.utils.js b/.storybook/storybook.utils.js new file mode 100644 index 000000000..855e6865e --- /dev/null +++ b/.storybook/storybook.utils.js @@ -0,0 +1,37 @@ +import * as knobsAddon from '@storybook/addon-knobs' + +const defaultKnobs = () => ({}) + +/** + * Create Props + * @param {{ [key: string]: any }} defaultData + * @param {String[]} allowedKnobs + * @param {Function} hydrateKnobs + */ +function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs) { + // Inject knobs add-on into given dependency container + const knobs = hydrateKnobs(defaultData, knobsAddon) + // Mix the available knob props into default data + const mixedProps = { ...defaultData, ...knobs } + + if (allowedKnobs.length === 0) { + return mixedProps + } + + return allowedKnobs.reduce((map, prop) => { + if (mixedProps.hasOwnProperty(prop)) { + const value = mixedProps[prop] + + // Knobs are functions which need calling to register them + if (typeof value === 'function') { + map[prop] = value() + } else { + map[prop] = value + } + } + + return map + }, {}) +} + +export default createProps diff --git a/components/x-gift-article/stories/index.js b/components/x-gift-article/stories/index.js deleted file mode 100644 index cdaf47d26..000000000 --- a/components/x-gift-article/stories/index.js +++ /dev/null @@ -1,18 +0,0 @@ -const { GiftArticle } = require('../') - -exports.component = GiftArticle - -exports.package = require('../package.json') - -exports.dependencies = { - 'o-fonts': '^3.0.0' -} - -exports.stories = [ - require('./with-gift-credits'), - require('./without-gift-credits'), - require('./with-gift-link'), - require('./free-article'), - require('./native-share'), - require('./error-response') -] diff --git a/components/x-gift-article/stories/error-response.js b/components/x-gift-article/storybook/error-response.js similarity index 100% rename from components/x-gift-article/stories/error-response.js rename to components/x-gift-article/storybook/error-response.js diff --git a/components/x-gift-article/stories/free-article.js b/components/x-gift-article/storybook/free-article.js similarity index 100% rename from components/x-gift-article/stories/free-article.js rename to components/x-gift-article/storybook/free-article.js diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx new file mode 100644 index 000000000..a11ed2249 --- /dev/null +++ b/components/x-gift-article/storybook/index.jsx @@ -0,0 +1,105 @@ +const { GiftArticle } = require('../dist/GiftArticle.cjs') +import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' +import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const pkg = require('../package.json') + +const dependencies = { + 'o-fonts': '^3.0.0' +} + +storiesOf('x-gift-article', module) + .addDecorator(withKnobs) + .add('With gift credits', () => { + const { data, knobs: storyKnobs } = require('./with-gift-credits') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('Without gift credits', () => { + const { data, knobs: storyKnobs } = require('./without-gift-credits') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('With gift link', () => { + const { data, knobs: storyKnobs } = require('./with-gift-link') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('Free article', () => { + const { data, knobs: storyKnobs } = require('./free-article') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('Native share', () => { + const { data, knobs: storyKnobs } = require('./native-share') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('Error response', () => { + const { data, knobs: storyKnobs } = require('./error-response') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) diff --git a/components/x-gift-article/stories/native-share.js b/components/x-gift-article/storybook/native-share.js similarity index 100% rename from components/x-gift-article/stories/native-share.js rename to components/x-gift-article/storybook/native-share.js diff --git a/components/x-gift-article/stories/with-gift-credits.js b/components/x-gift-article/storybook/with-gift-credits.js similarity index 100% rename from components/x-gift-article/stories/with-gift-credits.js rename to components/x-gift-article/storybook/with-gift-credits.js diff --git a/components/x-gift-article/stories/with-gift-link.js b/components/x-gift-article/storybook/with-gift-link.js similarity index 100% rename from components/x-gift-article/stories/with-gift-link.js rename to components/x-gift-article/storybook/with-gift-link.js diff --git a/components/x-gift-article/stories/without-gift-credits.js b/components/x-gift-article/storybook/without-gift-credits.js similarity index 100% rename from components/x-gift-article/stories/without-gift-credits.js rename to components/x-gift-article/storybook/without-gift-credits.js diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index cced81f06..d7fd6646b 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -4,7 +4,7 @@ import { withKnobs, text, boolean } from '@storybook/addon-knobs' import { LiveBlogPost } from '../src/LiveBlogPost' const defaultProps = { - id: 12345, + id: '12345', title: 'Turkey’s virus deaths may be 25% higher than official figure', bodyHTML: '<p>Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.</p>\n<p>Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.</p>\n<p>But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.</p>\n<p><a href="https://www.ft.com/content/80bb222c-b6eb-40ea-8014-563cbe9e0117" target="_blank">Read the article here</a></p>\n<p><img class="picture" src="http://blogs.ft.com/the-world/files/2020/05/istanbul_excess_morts_l.jpg"></p>', diff --git a/components/x-podcast-launchers/stories/index.js b/components/x-podcast-launchers/stories/index.js deleted file mode 100644 index 696d54f4c..000000000 --- a/components/x-podcast-launchers/stories/index.js +++ /dev/null @@ -1,17 +0,0 @@ -const { PodcastLaunchers } = require('../') - -exports.component = PodcastLaunchers - -exports.package = require('../package.json') - -// Set up basic document styling using the Origami build service -exports.dependencies = { - 'o-normalise': '^1.6.0', - 'o-typography': '^5.5.0', - 'o-buttons': '^5.16.6', - 'o-forms': '^7.0.0' -} - -exports.stories = [require('./example')] - -exports.knobs = require('./knobs') diff --git a/components/x-podcast-launchers/stories/example.js b/components/x-podcast-launchers/storybook/example.js similarity index 100% rename from components/x-podcast-launchers/stories/example.js rename to components/x-podcast-launchers/storybook/example.js diff --git a/components/x-podcast-launchers/storybook/index.jsx b/components/x-podcast-launchers/storybook/index.jsx new file mode 100644 index 000000000..adf5d2f85 --- /dev/null +++ b/components/x-podcast-launchers/storybook/index.jsx @@ -0,0 +1,36 @@ +const { PodcastLaunchers } = require('../dist/PodcastLaunchers.cjs') +import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' +import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const pkg = require('../package.json') + +// Set up basic document styling using the Origami build service +const dependencies = { + 'o-normalise': '^1.6.0', + 'o-typography': '^5.5.0', + 'o-buttons': '^5.16.6', + 'o-forms': '^7.0.0' +} + +const knobs = require('./knobs') + +storiesOf('x-podcast-launchers', module) + .addDecorator(withKnobs) + .add('Example', () => { + const { data, knobs: storyKnobs } = require('./example') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-podcast-launchers/${pkg.style}`} /> + </Helmet> + )} + <PodcastLaunchers {...props} /> + </div> + ) + }) diff --git a/components/x-podcast-launchers/stories/knobs.js b/components/x-podcast-launchers/storybook/knobs.js similarity index 100% rename from components/x-podcast-launchers/stories/knobs.js rename to components/x-podcast-launchers/storybook/knobs.js diff --git a/components/x-privacy-manager/storybook/index.js b/components/x-privacy-manager/storybook/index.js deleted file mode 100644 index 4146012ba..000000000 --- a/components/x-privacy-manager/storybook/index.js +++ /dev/null @@ -1,20 +0,0 @@ -const { PrivacyManager } = require('../src/privacy-manager') - -exports.component = PrivacyManager - -exports.package = require('../package.json') - -exports.dependencies = { - 'o-loading': '^4.0.0', - 'o-message': '^4.0.0', - 'o-typography': '^6.0.0' -} - -exports.stories = [ - require('./story-consent-indeterminate'), - require('./story-consent-accepted'), - require('./story-consent-blocked'), - require('./story-save-failed') -] - -exports.knobs = require('./knobs') diff --git a/components/x-privacy-manager/storybook/index.jsx b/components/x-privacy-manager/storybook/index.jsx new file mode 100644 index 000000000..bdb4c0f3b --- /dev/null +++ b/components/x-privacy-manager/storybook/index.jsx @@ -0,0 +1,79 @@ +const { PrivacyManager } = require('../src/privacy-manager') +import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' +import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const pkg = require('../package.json') + +const dependencies = { + 'o-loading': '^4.0.0', + 'o-message': '^4.0.0', + 'o-typography': '^6.0.0' +} + +const knobs = require('./knobs') + +storiesOf('x-privacy-manager', module) + .addDecorator(withKnobs) + .add('Consent: indeterminate', () => { + const { data, knobs: storyKnobs } = require('./story-consent-indeterminate') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) + }) + .add('Consent: accepted', () => { + const { data, knobs: storyKnobs } = require('./story-consent-accepted') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) + }) + .add('Consent: blocked', () => { + const { data, knobs: storyKnobs } = require('./story-consent-blocked') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) + }) + .add('Save failed', () => { + const { data, knobs: storyKnobs } = require('./story-save-failed') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) + }) diff --git a/components/x-styling-demo/stories/index.js b/components/x-styling-demo/stories/index.js deleted file mode 100644 index 525aeeb0f..000000000 --- a/components/x-styling-demo/stories/index.js +++ /dev/null @@ -1,15 +0,0 @@ -const { Button } = require('../') - -exports.component = Button -exports.package = require('../package.json') -exports.stories = [require('./styling')] - -exports.knobs = (data, { boolean }) => ({ - danger() { - return boolean('Danger', data.danger) - }, - - large() { - return boolean('Large', data.large) - } -}) diff --git a/components/x-styling-demo/storybook/index.jsx b/components/x-styling-demo/storybook/index.jsx new file mode 100644 index 000000000..af66b5489 --- /dev/null +++ b/components/x-styling-demo/storybook/index.jsx @@ -0,0 +1,36 @@ +const { Button } = require('../dist/Button.cjs') +import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' +import createProps from '../../../.storybook/storybook.utils' +const path = require('path') +const pkg = require('../package.json') +const name = path.basename(pkg.name) + +const knobs = (data, { boolean }) => ({ + danger() { + return boolean('Danger', data.danger) + }, + + large() { + return boolean('Large', data.large) + } +}) + +storiesOf('x-styling-demo', module) + .addDecorator(withKnobs) + .add('Styling', () => { + const { data, knobs: storyKnobs } = require('./styling') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Button {...props} /> + </div> + ) + }) diff --git a/components/x-styling-demo/stories/styling.js b/components/x-styling-demo/storybook/styling.js similarity index 100% rename from components/x-styling-demo/stories/styling.js rename to components/x-styling-demo/storybook/styling.js diff --git a/components/x-teaser-timeline/stories/index.js b/components/x-teaser-timeline/stories/index.js deleted file mode 100644 index 308fd5b86..000000000 --- a/components/x-teaser-timeline/stories/index.js +++ /dev/null @@ -1,16 +0,0 @@ -const { TeaserTimeline } = require('../') - -exports.component = TeaserTimeline - -exports.package = require('../package.json') - -// Set up basic document styling using the Origami build service -exports.dependencies = { - 'o-normalise': '^1.6.0', - 'o-typography': '^5.5.0', - 'o-teaser': '^2.3.1' -} - -exports.stories = [require('./timeline')] - -exports.knobs = require('./knobs') diff --git a/components/x-teaser-timeline/stories/content-items.json b/components/x-teaser-timeline/storybook/content-items.json similarity index 100% rename from components/x-teaser-timeline/stories/content-items.json rename to components/x-teaser-timeline/storybook/content-items.json diff --git a/components/x-teaser-timeline/storybook/index.jsx b/components/x-teaser-timeline/storybook/index.jsx new file mode 100644 index 000000000..e29c23561 --- /dev/null +++ b/components/x-teaser-timeline/storybook/index.jsx @@ -0,0 +1,34 @@ +const { TeaserTimeline } = require('../dist/TeaserTimeline.cjs') +import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' +import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const pkg = require('../package.json') + +const dependencies = { + 'o-normalise': '^1.6.0', + 'o-typography': '^5.5.0', + 'o-teaser': '^2.3.1' +} + +const knobs = require('./knobs') + +storiesOf('x-teaser-timeline', module) + .addDecorator(withKnobs) + .add('Timeline', () => { + const { data, knobs: storyKnobs } = require('./timeline') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-teaser-timeline/${pkg.style}`} /> + </Helmet> + )} + <TeaserTimeline {...props} /> + </div> + ) + }) diff --git a/components/x-teaser-timeline/stories/knobs.js b/components/x-teaser-timeline/storybook/knobs.js similarity index 100% rename from components/x-teaser-timeline/stories/knobs.js rename to components/x-teaser-timeline/storybook/knobs.js diff --git a/components/x-teaser-timeline/stories/timeline.js b/components/x-teaser-timeline/storybook/timeline.js similarity index 100% rename from components/x-teaser-timeline/stories/timeline.js rename to components/x-teaser-timeline/storybook/timeline.js diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index 5981e3d2c..e77037dfd 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -1,4 +1,3 @@ -const { URL, URLSearchParams } = require('url') const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw' const OPTIONS = { source: 'next', fit: 'scale-down', dpr: 2 } diff --git a/components/x-teaser/storybook/index.js b/components/x-teaser/storybook/index.js deleted file mode 100644 index 988edef19..000000000 --- a/components/x-teaser/storybook/index.js +++ /dev/null @@ -1,27 +0,0 @@ -const { Teaser } = require('../') - -exports.component = Teaser - -exports.package = require('../package.json') - -exports.dependencies = { - 'o-date': '^4.0.0', - 'o-labels': '^5.0.0', - 'o-normalise': '^2.0.0', - 'o-teaser': '^4.0.0', - 'o-typography': '^6.0.0', - 'o-video': '^6.0.0' -} - -exports.stories = [ - require('./article'), - require('./podcast'), - require('./opinion'), - require('./content-package'), - require('./package-item'), - require('./promoted'), - require('./top-story'), - require('./video') -] - -exports.knobs = require('./knobs') diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx new file mode 100644 index 000000000..209bf6d66 --- /dev/null +++ b/components/x-teaser/storybook/index.jsx @@ -0,0 +1,144 @@ +const { Teaser } = require('../') +import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' +import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const path = require('path') +const pkg = require('../package.json') +const name = path.basename(pkg.name) + +const dependencies = { + 'o-date': '^4.0.0', + 'o-labels': '^5.0.0', + 'o-normalise': '^2.0.0', + 'o-teaser': '^4.0.0', + 'o-typography': '^6.0.0', + 'o-video': '^6.0.0' +} + +const knobs = require('./knobs') + +storiesOf('x-teaser', module) + .addDecorator(withKnobs) + .add('Article', () => { + const { data, knobs: storyKnobs } = require('./article') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('Podcast', () => { + const { data, knobs: storyKnobs } = require('./podcast') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('Opinion', () => { + const { data, knobs: storyKnobs } = require('./opinion') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('ContentPackage', () => { + const { data, knobs: storyKnobs } = require('./content-package') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('PackageItem', () => { + const { data, knobs: storyKnobs } = require('./package-item') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('Promoted', () => { + const { data, knobs: storyKnobs } = require('./promoted') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('TopStory', () => { + const { data, knobs: storyKnobs } = require('./top-story') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('Video', () => { + const { data, knobs: storyKnobs } = require('./video') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) From 6ca9c9c16da11b69fefd9c897890fa41fa16d8c2 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Mon, 16 Nov 2020 17:29:49 +0000 Subject: [PATCH 602/760] convert config.js configuration to main.js --- .storybook/addons.js | 2 -- .storybook/config.js | 14 -------------- .storybook/main.js | 4 ++++ 3 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 .storybook/addons.js delete mode 100644 .storybook/config.js create mode 100644 .storybook/main.js diff --git a/.storybook/addons.js b/.storybook/addons.js deleted file mode 100644 index 41bc2666b..000000000 --- a/.storybook/addons.js +++ /dev/null @@ -1,2 +0,0 @@ -import '@storybook/addon-knobs/register' -import '@storybook/addon-viewport/register' diff --git a/.storybook/config.js b/.storybook/config.js deleted file mode 100644 index c0a20caa6..000000000 --- a/.storybook/config.js +++ /dev/null @@ -1,14 +0,0 @@ -import { configure } from '@storybook/react' - -configure(() => { - require('../components/x-increment/storybook/index.jsx') - require('../components/x-follow-button/storybook/index.jsx') - require('../components/x-live-blog-post/storybook/index.jsx') - require('../components/x-live-blog-wrapper/storybook/index.jsx') - require('../components/x-teaser/storybook/index.jsx') - require('../components/x-styling-demo/storybook/index.jsx') - require('../components/x-gift-article/storybook/index.jsx') - require('../components/x-podcast-launchers/storybook/index.jsx') - require('../components/x-teaser-timeline/storybook/index.jsx') - require('../components/x-privacy-manager/storybook/index.jsx') -}, module) diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 000000000..ef550510f --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,4 @@ +module.exports = { + stories: ['../components/**/storybook/index.jsx'], + addons: ['@storybook/addon-knobs', '@storybook/addon-viewport'] +} From 55e7db03d7a772013d373cae1f1908be8183c488 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Mon, 16 Nov 2020 17:41:14 +0000 Subject: [PATCH 603/760] convert storiesOf to Component Story Format --- .../x-follow-button/storybook/index.jsx | 28 +- components/x-gift-article/storybook/index.jsx | 217 ++++++++------ components/x-increment/storybook/index.jsx | 37 +-- .../x-live-blog-post/storybook/index.jsx | 37 +-- .../x-live-blog-wrapper/storybook/index.jsx | 25 +- .../x-podcast-launchers/storybook/index.jsx | 38 +-- .../x-privacy-manager/storybook/index.jsx | 147 +++++----- components/x-styling-demo/storybook/index.jsx | 36 +-- .../x-teaser-timeline/storybook/index.jsx | 38 +-- components/x-teaser/storybook/index.jsx | 267 ++++++++++-------- 10 files changed, 480 insertions(+), 390 deletions(-) diff --git a/components/x-follow-button/storybook/index.jsx b/components/x-follow-button/storybook/index.jsx index 98813d7c6..3cedc26aa 100644 --- a/components/x-follow-button/storybook/index.jsx +++ b/components/x-follow-button/storybook/index.jsx @@ -1,6 +1,5 @@ import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs, text, boolean, select } from '@storybook/addon-knobs' import { FollowButton } from '../src/FollowButton' @@ -23,16 +22,19 @@ const toggleFollowPlusDigestEmail = () => boolean('followPlusDigestEmail', defau const toggleVariant = () => select('variant', ['standard', 'inverse', 'opinion', 'monochrome'], defaultProps.variant) -storiesOf('x-follow-button', module) - .addDecorator(withKnobs) - .add('Follow Button', () => { - const knobs = { - conceptNameAsButtonText: toggleConceptNameAsButtonText(), - isFollowed: toggleIsFollowed(), - conceptName: toggleConceptName(), - followPlusDigestEmail: toggleFollowPlusDigestEmail(), - variant: toggleVariant() - } +export default { + title: 'x-follow-button', + decorators: [withKnobs] +} + +export const _FollowButton = () => { + const knobs = { + conceptNameAsButtonText: toggleConceptNameAsButtonText(), + isFollowed: toggleIsFollowed(), + conceptName: toggleConceptName(), + followPlusDigestEmail: toggleFollowPlusDigestEmail(), + variant: toggleVariant() + } - return <FollowButton {...defaultProps} {...knobs} /> - }) + return <FollowButton {...defaultProps} {...knobs} /> +} diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index a11ed2249..92858b941 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -1,6 +1,5 @@ const { GiftArticle } = require('../dist/GiftArticle.cjs') import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' @@ -11,95 +10,127 @@ const dependencies = { 'o-fonts': '^3.0.0' } -storiesOf('x-gift-article', module) - .addDecorator(withKnobs) - .add('With gift credits', () => { - const { data, knobs: storyKnobs } = require('./with-gift-credits') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('Without gift credits', () => { - const { data, knobs: storyKnobs } = require('./without-gift-credits') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('With gift link', () => { - const { data, knobs: storyKnobs } = require('./with-gift-link') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('Free article', () => { - const { data, knobs: storyKnobs } = require('./free-article') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('Native share', () => { - const { data, knobs: storyKnobs } = require('./native-share') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('Error response', () => { - const { data, knobs: storyKnobs } = require('./error-response') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) +export default { + title: 'x-gift-article', + decorators: [withKnobs] +} + +export const WithGiftCredits = () => { + const { data, knobs: storyKnobs } = require('./with-gift-credits') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) +} + +WithGiftCredits.story = { + name: 'With gift credits' +} + +export const WithoutGiftCredits = () => { + const { data, knobs: storyKnobs } = require('./without-gift-credits') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) +} + +WithoutGiftCredits.story = { + name: 'Without gift credits' +} + +export const WithGiftLink = () => { + const { data, knobs: storyKnobs } = require('./with-gift-link') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) +} + +WithGiftLink.story = { + name: 'With gift link' +} + +export const FreeArticle = () => { + const { data, knobs: storyKnobs } = require('./free-article') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) +} + +FreeArticle.story = { + name: 'Free article' +} + +export const NativeShare = () => { + const { data, knobs: storyKnobs } = require('./native-share') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) +} + +NativeShare.story = { + name: 'Native share' +} + +export const ErrorResponse = () => { + const { data, knobs: storyKnobs } = require('./error-response') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) +} + +ErrorResponse.story = { + name: 'Error response' +} diff --git a/components/x-increment/storybook/index.jsx b/components/x-increment/storybook/index.jsx index 31443a1e3..09ee6201f 100644 --- a/components/x-increment/storybook/index.jsx +++ b/components/x-increment/storybook/index.jsx @@ -1,22 +1,25 @@ import React from 'react' -import { storiesOf } from '@storybook/react' import { Increment } from '../src/Increment' -storiesOf('x-increment', module) - .add('Sync', () => { - const data = { - count: 1, - id: 'base-increment-static-id' - } +export default { + title: 'x-increment' +} - return <Increment {...data} /> - }) - .add('Async', () => { - const data = { - count: 1, - timeout: 1000, - id: 'base-increment-static-id' - } +export const Sync = () => { + const data = { + count: 1, + id: 'base-increment-static-id' + } - return <Increment {...data} /> - }) + return <Increment {...data} /> +} + +export const Async = () => { + const data = { + count: 1, + timeout: 1000, + id: 'base-increment-static-id' + } + + return <Increment {...data} /> +} diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index d7fd6646b..894658909 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -1,5 +1,4 @@ import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs, text, boolean } from '@storybook/addon-knobs' import { LiveBlogPost } from '../src/LiveBlogPost' @@ -22,23 +21,27 @@ const togglePublishedTimestamp = () => text('Published date', defaultProps.publi const toggleArticleUrl = () => text('Article URL', defaultProps.articleUrl) const toggleShowShareButtons = () => boolean('Show share buttons', defaultProps.showShareButtons) -storiesOf('x-live-blog-post', module) - .addDecorator(withKnobs) - .addParameters({ +export default { + title: 'x-live-blog-post', + decorators: [withKnobs], + + parameters: { knobs: { escapeHTML: false } - }) - .add('Content Body', () => { - const knobs = { - title: toggleTitle(), - isBreakingNews: toggleShowBreakingNews(), - bodyHTML: toggleContent(), - id: togglePostId(), - publishedDate: togglePublishedTimestamp(), - articleUrl: toggleArticleUrl(), - showShareButtons: toggleShowShareButtons() - } + } +} - return <LiveBlogPost {...defaultProps} {...knobs} /> - }) +export const ContentBody = () => { + const knobs = { + title: toggleTitle(), + isBreakingNews: toggleShowBreakingNews(), + bodyHTML: toggleContent(), + id: togglePostId(), + publishedDate: togglePublishedTimestamp(), + articleUrl: toggleArticleUrl(), + showShareButtons: toggleShowShareButtons() + } + + return <LiveBlogPost {...defaultProps} {...knobs} /> +} diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 148b938c7..10053ae33 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -1,5 +1,4 @@ import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs, text } from '@storybook/addon-knobs' import { LiveBlogWrapper } from '../src/LiveBlogWrapper' import '../../x-live-blog-post/dist/LiveBlogPost.css' @@ -39,17 +38,21 @@ const defaultProps = { const toggleMessage = () => text('Message', defaultProps.message) -storiesOf('x-live-blog-wrapper', module) - .addDecorator(withKnobs) - .addParameters({ +export default { + title: 'x-live-blog-wrapper', + decorators: [withKnobs], + + parameters: { knobs: { escapeHTML: false } - }) - .add('Content Body', () => { - const knobs = { - message: toggleMessage() - } + } +} - return <LiveBlogWrapper {...defaultProps} {...knobs} /> - }) +export const ContentBody = () => { + const knobs = { + message: toggleMessage() + } + + return <LiveBlogWrapper {...defaultProps} {...knobs} /> +} diff --git a/components/x-podcast-launchers/storybook/index.jsx b/components/x-podcast-launchers/storybook/index.jsx index adf5d2f85..2a497d80e 100644 --- a/components/x-podcast-launchers/storybook/index.jsx +++ b/components/x-podcast-launchers/storybook/index.jsx @@ -1,6 +1,5 @@ const { PodcastLaunchers } = require('../dist/PodcastLaunchers.cjs') import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' @@ -17,20 +16,23 @@ const dependencies = { const knobs = require('./knobs') -storiesOf('x-podcast-launchers', module) - .addDecorator(withKnobs) - .add('Example', () => { - const { data, knobs: storyKnobs } = require('./example') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-podcast-launchers/${pkg.style}`} /> - </Helmet> - )} - <PodcastLaunchers {...props} /> - </div> - ) - }) +export default { + title: 'x-podcast-launchers', + decorators: [withKnobs] +} + +export const Example = () => { + const { data, knobs: storyKnobs } = require('./example') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-podcast-launchers/${pkg.style}`} /> + </Helmet> + )} + <PodcastLaunchers {...props} /> + </div> + ) +} diff --git a/components/x-privacy-manager/storybook/index.jsx b/components/x-privacy-manager/storybook/index.jsx index bdb4c0f3b..7eb3c5da6 100644 --- a/components/x-privacy-manager/storybook/index.jsx +++ b/components/x-privacy-manager/storybook/index.jsx @@ -1,6 +1,5 @@ const { PrivacyManager } = require('../src/privacy-manager') import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' @@ -15,65 +14,87 @@ const dependencies = { const knobs = require('./knobs') -storiesOf('x-privacy-manager', module) - .addDecorator(withKnobs) - .add('Consent: indeterminate', () => { - const { data, knobs: storyKnobs } = require('./story-consent-indeterminate') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} - <PrivacyManager {...props} /> - </div> - ) - }) - .add('Consent: accepted', () => { - const { data, knobs: storyKnobs } = require('./story-consent-accepted') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} - <PrivacyManager {...props} /> - </div> - ) - }) - .add('Consent: blocked', () => { - const { data, knobs: storyKnobs } = require('./story-consent-blocked') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} - <PrivacyManager {...props} /> - </div> - ) - }) - .add('Save failed', () => { - const { data, knobs: storyKnobs } = require('./story-save-failed') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} - <PrivacyManager {...props} /> - </div> - ) - }) +export default { + title: 'x-privacy-manager', + decorators: [withKnobs] +} + +export const ConsentIndeterminate = () => { + const { data, knobs: storyKnobs } = require('./story-consent-indeterminate') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) +} + +ConsentIndeterminate.story = { + name: 'Consent: indeterminate' +} + +export const ConsentAccepted = () => { + const { data, knobs: storyKnobs } = require('./story-consent-accepted') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) +} + +ConsentAccepted.story = { + name: 'Consent: accepted' +} + +export const ConsentBlocked = () => { + const { data, knobs: storyKnobs } = require('./story-consent-blocked') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) +} + +ConsentBlocked.story = { + name: 'Consent: blocked' +} + +export const SaveFailed = () => { + const { data, knobs: storyKnobs } = require('./story-save-failed') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) +} + +SaveFailed.story = { + name: 'Save failed' +} diff --git a/components/x-styling-demo/storybook/index.jsx b/components/x-styling-demo/storybook/index.jsx index af66b5489..d4c8a93b4 100644 --- a/components/x-styling-demo/storybook/index.jsx +++ b/components/x-styling-demo/storybook/index.jsx @@ -1,6 +1,5 @@ const { Button } = require('../dist/Button.cjs') import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import createProps from '../../../.storybook/storybook.utils' @@ -18,19 +17,22 @@ const knobs = (data, { boolean }) => ({ } }) -storiesOf('x-styling-demo', module) - .addDecorator(withKnobs) - .add('Styling', () => { - const { data, knobs: storyKnobs } = require('./styling') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Button {...props} /> - </div> - ) - }) +export default { + title: 'x-styling-demo', + decorators: [withKnobs] +} + +export const Styling = () => { + const { data, knobs: storyKnobs } = require('./styling') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Button {...props} /> + </div> + ) +} diff --git a/components/x-teaser-timeline/storybook/index.jsx b/components/x-teaser-timeline/storybook/index.jsx index e29c23561..b01aae2a1 100644 --- a/components/x-teaser-timeline/storybook/index.jsx +++ b/components/x-teaser-timeline/storybook/index.jsx @@ -1,6 +1,5 @@ const { TeaserTimeline } = require('../dist/TeaserTimeline.cjs') import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' @@ -15,20 +14,23 @@ const dependencies = { const knobs = require('./knobs') -storiesOf('x-teaser-timeline', module) - .addDecorator(withKnobs) - .add('Timeline', () => { - const { data, knobs: storyKnobs } = require('./timeline') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-teaser-timeline/${pkg.style}`} /> - </Helmet> - )} - <TeaserTimeline {...props} /> - </div> - ) - }) +export default { + title: 'x-teaser-timeline', + decorators: [withKnobs] +} + +export const Timeline = () => { + const { data, knobs: storyKnobs } = require('./timeline') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-teaser-timeline/${pkg.style}`} /> + </Helmet> + )} + <TeaserTimeline {...props} /> + </div> + ) +} diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index 209bf6d66..4981ffbce 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -1,6 +1,5 @@ const { Teaser } = require('../') import React from 'react' -import { storiesOf } from '@storybook/react' import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' @@ -20,125 +19,147 @@ const dependencies = { const knobs = require('./knobs') -storiesOf('x-teaser', module) - .addDecorator(withKnobs) - .add('Article', () => { - const { data, knobs: storyKnobs } = require('./article') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('Podcast', () => { - const { data, knobs: storyKnobs } = require('./podcast') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('Opinion', () => { - const { data, knobs: storyKnobs } = require('./opinion') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('ContentPackage', () => { - const { data, knobs: storyKnobs } = require('./content-package') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('PackageItem', () => { - const { data, knobs: storyKnobs } = require('./package-item') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('Promoted', () => { - const { data, knobs: storyKnobs } = require('./promoted') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('TopStory', () => { - const { data, knobs: storyKnobs } = require('./top-story') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('Video', () => { - const { data, knobs: storyKnobs } = require('./video') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) +export default { + title: 'x-teaser', + decorators: [withKnobs] +} + +export const Article = () => { + const { data, knobs: storyKnobs } = require('./article') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) +} + +export const Podcast = () => { + const { data, knobs: storyKnobs } = require('./podcast') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) +} + +export const Opinion = () => { + const { data, knobs: storyKnobs } = require('./opinion') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) +} + +export const ContentPackage = () => { + const { data, knobs: storyKnobs } = require('./content-package') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) +} + +ContentPackage.story = { + name: 'ContentPackage' +} + +export const PackageItem = () => { + const { data, knobs: storyKnobs } = require('./package-item') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) +} + +PackageItem.story = { + name: 'PackageItem' +} + +export const Promoted = () => { + const { data, knobs: storyKnobs } = require('./promoted') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) +} + +export const TopStory = () => { + const { data, knobs: storyKnobs } = require('./top-story') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) +} + +TopStory.story = { + name: 'TopStory' +} + +export const Video = () => { + const { data, knobs: storyKnobs } = require('./video') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) +} From 50cec48352e56629438795e6549a1cf7e5104b80 Mon Sep 17 00:00:00 2001 From: andygout <andygout@hotmail.co.uk> Date: Tue, 17 Nov 2020 08:16:08 +0000 Subject: [PATCH 604/760] Remove Snyk monitor command from CircleCI config --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4fba0763d..eb72d3884 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,7 +124,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} @@ -139,7 +138,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Extract tag name and version number command: | From 462546a922c3af195ba2fee5025c53a14b1f5bf0 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Wed, 18 Nov 2020 18:17:37 +0000 Subject: [PATCH 605/760] migrate storybook knobs to controls --- .storybook/main.js | 2 +- .storybook/storybook.utils.js | 37 --- .../x-follow-button/storybook/index.jsx | 43 +-- .../storybook/error-response.js | 4 +- .../x-gift-article/storybook/free-article.js | 4 +- components/x-gift-article/storybook/index.jsx | 47 ++-- .../x-gift-article/storybook/native-share.js | 4 +- .../storybook/with-gift-credits.js | 4 +- .../storybook/with-gift-link.js | 4 +- .../storybook/without-gift-credits.js | 4 +- .../x-live-blog-post/storybook/index.jsx | 49 +--- .../x-live-blog-wrapper/storybook/index.jsx | 19 +- .../x-podcast-launchers/storybook/example.js | 16 -- .../x-podcast-launchers/storybook/index.jsx | 23 +- .../x-podcast-launchers/storybook/knobs.js | 5 - .../x-privacy-manager/storybook/data.js | 44 ++- .../x-privacy-manager/storybook/index.jsx | 47 ++-- .../x-privacy-manager/storybook/knobs.js | 38 --- .../storybook/story-consent-accepted.js | 16 -- .../storybook/story-consent-blocked.js | 16 -- .../storybook/story-consent-indeterminate.js | 16 -- .../storybook/story-save-failed.js | 13 - components/x-styling-demo/storybook/index.jsx | 22 +- .../x-styling-demo/storybook/styling.js | 12 - components/x-teaser-timeline/src/lib/date.js | 6 +- .../x-teaser-timeline/storybook/index.jsx | 17 +- .../x-teaser-timeline/storybook/knobs.js | 19 -- .../x-teaser-timeline/storybook/timeline.js | 13 +- components/x-teaser/__fixtures__/article.json | 8 +- .../__fixtures__/content-package.json | 8 +- components/x-teaser/__fixtures__/opinion.json | 8 +- .../x-teaser/__fixtures__/package-item.json | 8 +- components/x-teaser/__fixtures__/podcast.json | 10 +- .../x-teaser/__fixtures__/promoted.json | 8 +- .../x-teaser/__fixtures__/top-story.json | 8 +- components/x-teaser/storybook/argTypes.js | 51 ++++ components/x-teaser/storybook/article.js | 46 +--- .../x-teaser/storybook/content-package.js | 38 +-- components/x-teaser/storybook/index.jsx | 73 +++-- components/x-teaser/storybook/knobs.js | 253 ------------------ components/x-teaser/storybook/opinion.js | 45 +--- components/x-teaser/storybook/package-item.js | 43 +-- components/x-teaser/storybook/podcast.js | 46 +--- components/x-teaser/storybook/promoted.js | 33 +-- components/x-teaser/storybook/top-story.js | 42 +-- components/x-teaser/storybook/video.js | 40 +-- package.json | 6 +- 47 files changed, 306 insertions(+), 1012 deletions(-) delete mode 100644 .storybook/storybook.utils.js delete mode 100644 components/x-podcast-launchers/storybook/example.js delete mode 100644 components/x-podcast-launchers/storybook/knobs.js delete mode 100644 components/x-privacy-manager/storybook/knobs.js delete mode 100644 components/x-privacy-manager/storybook/story-consent-accepted.js delete mode 100644 components/x-privacy-manager/storybook/story-consent-blocked.js delete mode 100644 components/x-privacy-manager/storybook/story-consent-indeterminate.js delete mode 100644 components/x-privacy-manager/storybook/story-save-failed.js delete mode 100644 components/x-styling-demo/storybook/styling.js delete mode 100644 components/x-teaser-timeline/storybook/knobs.js create mode 100644 components/x-teaser/storybook/argTypes.js delete mode 100644 components/x-teaser/storybook/knobs.js diff --git a/.storybook/main.js b/.storybook/main.js index ef550510f..1f24b1d0f 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,4 +1,4 @@ module.exports = { stories: ['../components/**/storybook/index.jsx'], - addons: ['@storybook/addon-knobs', '@storybook/addon-viewport'] + addons: ['@storybook/addon-controls'] } diff --git a/.storybook/storybook.utils.js b/.storybook/storybook.utils.js deleted file mode 100644 index 855e6865e..000000000 --- a/.storybook/storybook.utils.js +++ /dev/null @@ -1,37 +0,0 @@ -import * as knobsAddon from '@storybook/addon-knobs' - -const defaultKnobs = () => ({}) - -/** - * Create Props - * @param {{ [key: string]: any }} defaultData - * @param {String[]} allowedKnobs - * @param {Function} hydrateKnobs - */ -function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs) { - // Inject knobs add-on into given dependency container - const knobs = hydrateKnobs(defaultData, knobsAddon) - // Mix the available knob props into default data - const mixedProps = { ...defaultData, ...knobs } - - if (allowedKnobs.length === 0) { - return mixedProps - } - - return allowedKnobs.reduce((map, prop) => { - if (mixedProps.hasOwnProperty(prop)) { - const value = mixedProps[prop] - - // Knobs are functions which need calling to register them - if (typeof value === 'function') { - map[prop] = value() - } else { - map[prop] = value - } - } - - return map - }, {}) -} - -export default createProps diff --git a/components/x-follow-button/storybook/index.jsx b/components/x-follow-button/storybook/index.jsx index 3cedc26aa..d9947c135 100644 --- a/components/x-follow-button/storybook/index.jsx +++ b/components/x-follow-button/storybook/index.jsx @@ -1,40 +1,21 @@ import React from 'react' +import { FollowButton } from '../src/FollowButton' -import { withKnobs, text, boolean, select } from '@storybook/addon-knobs' +export default { + title: 'x-follow-button' +} -import { FollowButton } from '../src/FollowButton' +export const _FollowButton = (args) => { + return <FollowButton {...args} /> +} -const defaultProps = { - isFollowed: false, - variant: 'standard', +_FollowButton.args = { conceptNameAsButtonText: false, - conceptId: '00000-0000-00000-00000', + isFollowed: false, conceptName: 'UK politics & policy', followPlusDigestEmail: true, - csrfToken: 'testTokenValue' + variant: 'standard' } - -const toggleConceptNameAsButtonText = () => - boolean('conceptNameAsButtonText', defaultProps.conceptNameAsButtonText) -const toggleIsFollowed = () => boolean('isFollowed', defaultProps.isFollowed) -const toggleConceptName = () => text('Topic name', defaultProps.conceptName) -const toggleFollowPlusDigestEmail = () => boolean('followPlusDigestEmail', defaultProps.followPlusDigestEmail) -const toggleVariant = () => - select('variant', ['standard', 'inverse', 'opinion', 'monochrome'], defaultProps.variant) - -export default { - title: 'x-follow-button', - decorators: [withKnobs] -} - -export const _FollowButton = () => { - const knobs = { - conceptNameAsButtonText: toggleConceptNameAsButtonText(), - isFollowed: toggleIsFollowed(), - conceptName: toggleConceptName(), - followPlusDigestEmail: toggleFollowPlusDigestEmail(), - variant: toggleVariant() - } - - return <FollowButton {...defaultProps} {...knobs} /> +_FollowButton.argTypes = { + variant: { control: { type: 'select', options: ['standard', 'inverse', 'opinion', 'monochrome'] } } } diff --git a/components/x-gift-article/storybook/error-response.js b/components/x-gift-article/storybook/error-response.js index 190b8879d..19674241a 100644 --- a/components/x-gift-article/storybook/error-response.js +++ b/components/x-gift-article/storybook/error-response.js @@ -1,9 +1,7 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah' const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'With a bad response from membership APIs' - -exports.data = { +exports.args = { title: 'Share this article (unable to fetch credits)', isFreeArticle: false, article: { diff --git a/components/x-gift-article/storybook/free-article.js b/components/x-gift-article/storybook/free-article.js index e80ca5069..1bcfd3d11 100644 --- a/components/x-gift-article/storybook/free-article.js +++ b/components/x-gift-article/storybook/free-article.js @@ -1,9 +1,7 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah' const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'Free article' - -exports.data = { +exports.args = { title: 'Share this article (free)', isFreeArticle: true, article: { diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index 92858b941..b8f7c6c61 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -1,9 +1,7 @@ const { GiftArticle } = require('../dist/GiftArticle.cjs') import React from 'react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' const pkg = require('../package.json') const dependencies = { @@ -11,13 +9,10 @@ const dependencies = { } export default { - title: 'x-gift-article', - decorators: [withKnobs] + title: 'x-gift-article' } -export const WithGiftCredits = () => { - const { data, knobs: storyKnobs } = require('./with-gift-credits') - const props = createProps(data, storyKnobs) +export const WithGiftCredits = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -26,7 +21,7 @@ export const WithGiftCredits = () => { <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> </Helmet> )} - <GiftArticle {...props} /> + <GiftArticle {...args} /> </div> ) } @@ -34,10 +29,9 @@ export const WithGiftCredits = () => { WithGiftCredits.story = { name: 'With gift credits' } +WithGiftCredits.args = require('./with-gift-credits').args -export const WithoutGiftCredits = () => { - const { data, knobs: storyKnobs } = require('./without-gift-credits') - const props = createProps(data, storyKnobs) +export const WithoutGiftCredits = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -46,7 +40,7 @@ export const WithoutGiftCredits = () => { <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> </Helmet> )} - <GiftArticle {...props} /> + <GiftArticle {...args} /> </div> ) } @@ -54,10 +48,9 @@ export const WithoutGiftCredits = () => { WithoutGiftCredits.story = { name: 'Without gift credits' } +WithoutGiftCredits.args = require('./without-gift-credits').args -export const WithGiftLink = () => { - const { data, knobs: storyKnobs } = require('./with-gift-link') - const props = createProps(data, storyKnobs) +export const WithGiftLink = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -66,7 +59,7 @@ export const WithGiftLink = () => { <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> </Helmet> )} - <GiftArticle {...props} /> + <GiftArticle {...args} /> </div> ) } @@ -74,10 +67,9 @@ export const WithGiftLink = () => { WithGiftLink.story = { name: 'With gift link' } +WithGiftLink.args = require('./with-gift-link').args -export const FreeArticle = () => { - const { data, knobs: storyKnobs } = require('./free-article') - const props = createProps(data, storyKnobs) +export const FreeArticle = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -86,7 +78,7 @@ export const FreeArticle = () => { <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> </Helmet> )} - <GiftArticle {...props} /> + <GiftArticle {...args} /> </div> ) } @@ -94,10 +86,9 @@ export const FreeArticle = () => { FreeArticle.story = { name: 'Free article' } +FreeArticle.args = require('./free-article').args -export const NativeShare = () => { - const { data, knobs: storyKnobs } = require('./native-share') - const props = createProps(data, storyKnobs) +export const NativeShare = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -106,7 +97,7 @@ export const NativeShare = () => { <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> </Helmet> )} - <GiftArticle {...props} /> + <GiftArticle {...args} /> </div> ) } @@ -114,10 +105,9 @@ export const NativeShare = () => { NativeShare.story = { name: 'Native share' } +NativeShare.args = require('./native-share').args -export const ErrorResponse = () => { - const { data, knobs: storyKnobs } = require('./error-response') - const props = createProps(data, storyKnobs) +export const ErrorResponse = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -126,7 +116,7 @@ export const ErrorResponse = () => { <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> </Helmet> )} - <GiftArticle {...props} /> + <GiftArticle {...args} /> </div> ) } @@ -134,3 +124,4 @@ export const ErrorResponse = () => { ErrorResponse.story = { name: 'Error response' } +ErrorResponse.args = require('./error-response').args diff --git a/components/x-gift-article/storybook/native-share.js b/components/x-gift-article/storybook/native-share.js index 2c8ca9030..be1f2967d 100644 --- a/components/x-gift-article/storybook/native-share.js +++ b/components/x-gift-article/storybook/native-share.js @@ -3,9 +3,7 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah' const articleUrlRedeemed = 'https://gift-url-redeemed' const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'With native share on App' - -exports.data = { +exports.args = { title: 'Share this article (on App)', isFreeArticle: false, article: { diff --git a/components/x-gift-article/storybook/with-gift-credits.js b/components/x-gift-article/storybook/with-gift-credits.js index 7dc43e6a6..d520e3489 100644 --- a/components/x-gift-article/storybook/with-gift-credits.js +++ b/components/x-gift-article/storybook/with-gift-credits.js @@ -3,9 +3,7 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah' const articleUrlRedeemed = 'https://gift-url-redeemed' const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'With gift credits' - -exports.data = { +exports.args = { title: 'Share this article (with credit)', isFreeArticle: false, article: { diff --git a/components/x-gift-article/storybook/with-gift-link.js b/components/x-gift-article/storybook/with-gift-link.js index a5c279197..cfb57a86a 100644 --- a/components/x-gift-article/storybook/with-gift-link.js +++ b/components/x-gift-article/storybook/with-gift-link.js @@ -2,9 +2,7 @@ const articleId = 'article id' const articleUrl = 'https://www.ft.com/content/blahblahblah' const articleUrlRedeemed = 'https://gift-url-redeemed' -exports.title = 'With gift link' - -exports.data = { +exports.args = { title: 'Share this article (with gift link)', isFreeArticle: false, isGiftUrlCreated: true, diff --git a/components/x-gift-article/storybook/without-gift-credits.js b/components/x-gift-article/storybook/without-gift-credits.js index 4eb2226f4..cea0a4069 100644 --- a/components/x-gift-article/storybook/without-gift-credits.js +++ b/components/x-gift-article/storybook/without-gift-credits.js @@ -1,9 +1,7 @@ const articleUrl = 'https://www.ft.com/content/blahblahblah' const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` -exports.title = 'Without gift credits' - -exports.data = { +exports.args = { title: 'Share this article (without credit)', isFreeArticle: false, article: { diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index 894658909..2108130ee 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -1,47 +1,24 @@ import React from 'react' -import { withKnobs, text, boolean } from '@storybook/addon-knobs' import { LiveBlogPost } from '../src/LiveBlogPost' -const defaultProps = { - id: '12345', - title: 'Turkey’s virus deaths may be 25% higher than official figure', - bodyHTML: - '<p>Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.</p>\n<p>Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.</p>\n<p>But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.</p>\n<p><a href="https://www.ft.com/content/80bb222c-b6eb-40ea-8014-563cbe9e0117" target="_blank">Read the article here</a></p>\n<p><img class="picture" src="http://blogs.ft.com/the-world/files/2020/05/istanbul_excess_morts_l.jpg"></p>', - isBreakingNews: false, - publishedDate: '2020-05-13T18:52:28.000Z', - articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', - showShareButtons: true -} - -const toggleTitle = () => text('Title', defaultProps.title) -const toggleShowBreakingNews = () => boolean('Show breaking news', defaultProps.isBreakingNews) -const toggleContent = () => text('Body HTML', defaultProps.bodyHTML) -const togglePostId = () => text('ID', defaultProps.id) -const togglePublishedTimestamp = () => text('Published date', defaultProps.publishedDate) -const toggleArticleUrl = () => text('Article URL', defaultProps.articleUrl) -const toggleShowShareButtons = () => boolean('Show share buttons', defaultProps.showShareButtons) - export default { title: 'x-live-blog-post', - decorators: [withKnobs], - parameters: { - knobs: { - escapeHTML: false - } + escapeHTML: false } } -export const ContentBody = () => { - const knobs = { - title: toggleTitle(), - isBreakingNews: toggleShowBreakingNews(), - bodyHTML: toggleContent(), - id: togglePostId(), - publishedDate: togglePublishedTimestamp(), - articleUrl: toggleArticleUrl(), - showShareButtons: toggleShowShareButtons() - } +export const ContentBody = (args) => { + return <LiveBlogPost {...args} /> +} - return <LiveBlogPost {...defaultProps} {...knobs} /> +ContentBody.args = { + title: 'Turkey’s virus deaths may be 25% higher than official figure', + isBreakingNews: false, + bodyHTML: + '<p>Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.</p>\n<p>Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.</p>\n<p>But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.</p>\n<p><a href="https://www.ft.com/content/80bb222c-b6eb-40ea-8014-563cbe9e0117" target="_blank">Read the article here</a></p>\n<p><img class="picture" src="http://blogs.ft.com/the-world/files/2020/05/istanbul_excess_morts_l.jpg"></p>', + id: '12345', + publishedDate: '2020-05-13T18:52:28.000Z', + articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', + showShareButtons: true } diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 10053ae33..250b8f2a5 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -1,5 +1,4 @@ import React from 'react' -import { withKnobs, text } from '@storybook/addon-knobs' import { LiveBlogWrapper } from '../src/LiveBlogWrapper' import '../../x-live-blog-post/dist/LiveBlogPost.css' @@ -36,23 +35,15 @@ const defaultProps = { ] } -const toggleMessage = () => text('Message', defaultProps.message) - export default { title: 'x-live-blog-wrapper', - decorators: [withKnobs], - parameters: { - knobs: { - escapeHTML: false - } + escapeHTML: false } } -export const ContentBody = () => { - const knobs = { - message: toggleMessage() - } - - return <LiveBlogWrapper {...defaultProps} {...knobs} /> +export const ContentBody = (args) => { + return <LiveBlogWrapper {...args} /> } + +ContentBody.args = defaultProps diff --git a/components/x-podcast-launchers/storybook/example.js b/components/x-podcast-launchers/storybook/example.js deleted file mode 100644 index 6b4e91494..000000000 --- a/components/x-podcast-launchers/storybook/example.js +++ /dev/null @@ -1,16 +0,0 @@ -const { brand } = require('@financial-times/n-concept-ids') - -exports.title = 'Example' - -exports.data = { - conceptId: brand.rachmanReviewPodcast, - conceptName: 'Rachman Review', - isFollowed: false, - csrfToken: 'token', - acastRSSHost: 'https://access.acast.com', - acastAccessToken: 'abc-123' -} - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module diff --git a/components/x-podcast-launchers/storybook/index.jsx b/components/x-podcast-launchers/storybook/index.jsx index 2a497d80e..8bcb0e54f 100644 --- a/components/x-podcast-launchers/storybook/index.jsx +++ b/components/x-podcast-launchers/storybook/index.jsx @@ -1,9 +1,8 @@ const { PodcastLaunchers } = require('../dist/PodcastLaunchers.cjs') +const { brand } = require('@financial-times/n-concept-ids') import React from 'react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' const pkg = require('../package.json') // Set up basic document styling using the Origami build service @@ -14,16 +13,11 @@ const dependencies = { 'o-forms': '^7.0.0' } -const knobs = require('./knobs') - export default { - title: 'x-podcast-launchers', - decorators: [withKnobs] + title: 'x-podcast-launchers' } -export const Example = () => { - const { data, knobs: storyKnobs } = require('./example') - const props = createProps(data, storyKnobs, knobs) +export const Example = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -32,7 +26,16 @@ export const Example = () => { <link rel="stylesheet" href={`components/x-podcast-launchers/${pkg.style}`} /> </Helmet> )} - <PodcastLaunchers {...props} /> + <PodcastLaunchers {...args} /> </div> ) } + +Example.args = { + conceptId: brand.rachmanReviewPodcast, + conceptName: 'Rachman Review', + isFollowed: false, + csrfToken: 'token', + acastRSSHost: 'https://access.acast.com', + acastAccessToken: 'abc-123' +} diff --git a/components/x-podcast-launchers/storybook/knobs.js b/components/x-podcast-launchers/storybook/knobs.js deleted file mode 100644 index 4f9ada23e..000000000 --- a/components/x-podcast-launchers/storybook/knobs.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = (data, { text }) => ({ - conceptId: text('Concept id', data.conceptId), - acastRSSHost: text('Acast RSS host', data.acastRSSHost), - acastAccessToken: text('Acast Access token', data.acastAccessToken) -}) diff --git a/components/x-privacy-manager/storybook/data.js b/components/x-privacy-manager/storybook/data.js index 6fd4dd0d8..2bfc1ec4a 100644 --- a/components/x-privacy-manager/storybook/data.js +++ b/components/x-privacy-manager/storybook/data.js @@ -1,10 +1,33 @@ const CONSENT_API = 'https://consent.ft.com' -const defaults = { +const referrers = { + 'ft.com': 'www.ft.com', + 'exec-appointments.com': 'www.exec-appointments.com', + 'fdibenchmark.com': 'www.fdibenchmark.com', + 'fdiintelligence.com': 'www.fdiintelligence.com', + 'fdimarkets.com': 'www.fdimarkets.com', + 'fdireports.com': 'www.fdireports.com', + 'ftadviser.com': 'www.ftadviser.com', + 'ftconfidentialresearch.com': 'www.ftconfidentialresearch.com', + 'globalriskregulator.com': 'www.globalriskregulator.com', + 'investorschronicle.co.uk': 'www.investorschronicle.co.uk', + 'non-execs.com': 'www.non-execs.com', + 'pensions-expert.com': 'www.pensions-expert.com', + 'pwmnet.com': 'www.pwmnet.com', + 'thebanker.com': 'www.thebanker.com', + 'thebankerdatabase.com': 'www.thebankerdatabase.com', + Undefined: '' +} + +const legislation = { + CCPA: ['ccpa', 'gdpr'] +} + +const defaultArgs = { userId: 'fakeUserId', - consent: true, - legislation: [], - referrer: 'ft.com', + consent: undefined, + legislation: 'ccpa', + referrer: 'www.ft.com', consentProxyEndpoints: { core: CONSENT_API, enhanced: CONSENT_API, @@ -12,6 +35,16 @@ const defaults = { } } +const defaultArgTypes = { + userId: { + name: 'Authentication', + control: { type: 'select', options: { loggedIn: defaultArgs.userId, loggedOut: undefined } } + }, + legislation: { control: { type: 'select', options: legislation['CCPA'] } }, + referrer: { control: { type: 'select', options: referrers } }, + consent: { control: { type: 'boolean' }, name: 'consent' } +} + const getFetchMock = (status = 200, options = {}) => (fetchMock) => { fetchMock.mock(CONSENT_API, status, { delay: 1000, @@ -21,6 +54,7 @@ const getFetchMock = (status = 200, options = {}) => (fetchMock) => { module.exports = { CONSENT_API, - defaults, + defaultArgs, + defaultArgTypes, getFetchMock } diff --git a/components/x-privacy-manager/storybook/index.jsx b/components/x-privacy-manager/storybook/index.jsx index 7eb3c5da6..4b2a97cf1 100644 --- a/components/x-privacy-manager/storybook/index.jsx +++ b/components/x-privacy-manager/storybook/index.jsx @@ -1,9 +1,8 @@ const { PrivacyManager } = require('../src/privacy-manager') +const { defaultArgs, defaultArgTypes } = require('./data') import React from 'react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' const pkg = require('../package.json') const dependencies = { @@ -12,16 +11,11 @@ const dependencies = { 'o-typography': '^6.0.0' } -const knobs = require('./knobs') - export default { - title: 'x-privacy-manager', - decorators: [withKnobs] + title: 'x-privacy-manager' } -export const ConsentIndeterminate = () => { - const { data, knobs: storyKnobs } = require('./story-consent-indeterminate') - const props = createProps(data, storyKnobs, knobs) +export const ConsentIndeterminate = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -30,7 +24,7 @@ export const ConsentIndeterminate = () => { <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> </Helmet> )} - <PrivacyManager {...props} /> + <PrivacyManager {...args} /> </div> ) } @@ -39,9 +33,10 @@ ConsentIndeterminate.story = { name: 'Consent: indeterminate' } -export const ConsentAccepted = () => { - const { data, knobs: storyKnobs } = require('./story-consent-accepted') - const props = createProps(data, storyKnobs, knobs) +ConsentIndeterminate.args = defaultArgs +ConsentIndeterminate.argTypes = defaultArgTypes + +export const ConsentAccepted = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -50,7 +45,7 @@ export const ConsentAccepted = () => { <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> </Helmet> )} - <PrivacyManager {...props} /> + <PrivacyManager {...args} /> </div> ) } @@ -58,10 +53,13 @@ export const ConsentAccepted = () => { ConsentAccepted.story = { name: 'Consent: accepted' } +ConsentAccepted.args = { + ...defaultArgs, + consent: true +} +ConsentAccepted.argTypes = defaultArgTypes -export const ConsentBlocked = () => { - const { data, knobs: storyKnobs } = require('./story-consent-blocked') - const props = createProps(data, storyKnobs, knobs) +export const ConsentBlocked = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -70,7 +68,7 @@ export const ConsentBlocked = () => { <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> </Helmet> )} - <PrivacyManager {...props} /> + <PrivacyManager {...args} /> </div> ) } @@ -78,10 +76,13 @@ export const ConsentBlocked = () => { ConsentBlocked.story = { name: 'Consent: blocked' } +ConsentBlocked.args = { + ...defaultArgs, + consent: false +} +ConsentBlocked.argTypes = defaultArgTypes -export const SaveFailed = () => { - const { data, knobs: storyKnobs } = require('./story-save-failed') - const props = createProps(data, storyKnobs, knobs) +export const SaveFailed = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -90,7 +91,7 @@ export const SaveFailed = () => { <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> </Helmet> )} - <PrivacyManager {...props} /> + <PrivacyManager {...args} /> </div> ) } @@ -98,3 +99,5 @@ export const SaveFailed = () => { SaveFailed.story = { name: 'Save failed' } +SaveFailed.args = defaultArgs +SaveFailed.argTypes = defaultArgTypes diff --git a/components/x-privacy-manager/storybook/knobs.js b/components/x-privacy-manager/storybook/knobs.js deleted file mode 100644 index a4adc773b..000000000 --- a/components/x-privacy-manager/storybook/knobs.js +++ /dev/null @@ -1,38 +0,0 @@ -const legislation = { - CCPA: ['ccpa', 'gdpr'] - // GDPR: ['gdpr'] -} - -const referrers = { - 'ft.com': 'www.ft.com', - 'exec-appointments.com': 'www.exec-appointments.com', - 'fdibenchmark.com': 'www.fdibenchmark.com', - 'fdiintelligence.com': 'www.fdiintelligence.com', - 'fdimarkets.com': 'www.fdimarkets.com', - 'fdireports.com': 'www.fdireports.com', - 'ftadviser.com': 'www.ftadviser.com', - 'ftconfidentialresearch.com': 'www.ftconfidentialresearch.com', - 'globalriskregulator.com': 'www.globalriskregulator.com', - 'investorschronicle.co.uk': 'www.investorschronicle.co.uk', - 'non-execs.com': 'www.non-execs.com', - 'pensions-expert.com': 'www.pensions-expert.com', - 'pwmnet.com': 'www.pwmnet.com', - 'thebanker.com': 'www.thebanker.com', - 'thebankerdatabase.com': 'www.thebankerdatabase.com', - Undefined: '' -} - -module.exports = (data, { boolean, select }) => ({ - userId() { - return select('Authenticated', { loggedIn: data.userId, loggedOut: undefined }) - }, - consent() { - return boolean('Consent', data.consent, undefined) - }, - legislation() { - return select('Legislation', legislation, legislation['CCPA']) - }, - referrer() { - return select('Referrer', referrers, referrers['ft.com']) - } -}) diff --git a/components/x-privacy-manager/storybook/story-consent-accepted.js b/components/x-privacy-manager/storybook/story-consent-accepted.js deleted file mode 100644 index 69a3b1b2e..000000000 --- a/components/x-privacy-manager/storybook/story-consent-accepted.js +++ /dev/null @@ -1,16 +0,0 @@ -const { defaults, getFetchMock } = require('./data') - -exports.title = 'Consent: accepted' - -exports.data = { - ...defaults, - consent: true -} - -exports.knobs = Object.keys(exports.data) - -exports.fetchMock = getFetchMock() - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module diff --git a/components/x-privacy-manager/storybook/story-consent-blocked.js b/components/x-privacy-manager/storybook/story-consent-blocked.js deleted file mode 100644 index f6d68ec5f..000000000 --- a/components/x-privacy-manager/storybook/story-consent-blocked.js +++ /dev/null @@ -1,16 +0,0 @@ -const { defaults, getFetchMock } = require('./data') - -exports.title = 'Consent: blocked' - -exports.data = { - ...defaults, - consent: false -} - -exports.knobs = Object.keys(exports.data) - -exports.fetchMock = getFetchMock() - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module diff --git a/components/x-privacy-manager/storybook/story-consent-indeterminate.js b/components/x-privacy-manager/storybook/story-consent-indeterminate.js deleted file mode 100644 index e93495bfe..000000000 --- a/components/x-privacy-manager/storybook/story-consent-indeterminate.js +++ /dev/null @@ -1,16 +0,0 @@ -const { defaults, getFetchMock } = require('./data') - -exports.title = 'Consent: indeterminate' - -exports.data = { - ...defaults, - consent: undefined -} - -exports.knobs = Object.keys(exports.data) - -exports.fetchMock = getFetchMock() - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module diff --git a/components/x-privacy-manager/storybook/story-save-failed.js b/components/x-privacy-manager/storybook/story-save-failed.js deleted file mode 100644 index ecbf445ae..000000000 --- a/components/x-privacy-manager/storybook/story-save-failed.js +++ /dev/null @@ -1,13 +0,0 @@ -const { defaults, getFetchMock } = require('./data') - -exports.title = 'Save failed' - -exports.data = defaults - -exports.knobs = Object.keys(exports.data) - -exports.fetchMock = getFetchMock(500) - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module diff --git a/components/x-styling-demo/storybook/index.jsx b/components/x-styling-demo/storybook/index.jsx index d4c8a93b4..bd75d3087 100644 --- a/components/x-styling-demo/storybook/index.jsx +++ b/components/x-styling-demo/storybook/index.jsx @@ -1,30 +1,15 @@ const { Button } = require('../dist/Button.cjs') import React from 'react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' -import createProps from '../../../.storybook/storybook.utils' const path = require('path') const pkg = require('../package.json') const name = path.basename(pkg.name) -const knobs = (data, { boolean }) => ({ - danger() { - return boolean('Danger', data.danger) - }, - - large() { - return boolean('Large', data.large) - } -}) - export default { - title: 'x-styling-demo', - decorators: [withKnobs] + title: 'x-styling-demo' } -export const Styling = () => { - const { data, knobs: storyKnobs } = require('./styling') - const props = createProps(data, storyKnobs, knobs) +export const Styling = (args) => { return ( <div className="story-container"> {pkg.style && ( @@ -32,7 +17,8 @@ export const Styling = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Button {...props} /> + <Button {...args} /> </div> ) } +Styling.args = { danger: false, large: false } diff --git a/components/x-styling-demo/storybook/styling.js b/components/x-styling-demo/storybook/styling.js deleted file mode 100644 index 3a0f07633..000000000 --- a/components/x-styling-demo/storybook/styling.js +++ /dev/null @@ -1,12 +0,0 @@ -exports.title = 'Styling' - -exports.data = { - danger: false, - large: false -} - -exports.knobs = ['danger', 'large'] - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module diff --git a/components/x-teaser-timeline/src/lib/date.js b/components/x-teaser-timeline/src/lib/date.js index 70bf95598..2b8fd7d40 100644 --- a/components/x-teaser-timeline/src/lib/date.js +++ b/components/x-teaser-timeline/src/lib/date.js @@ -48,4 +48,8 @@ export const splitLatestEarlier = (items, splitDate) => { return { latestItems, earlierItems } } -export const getDateOnly = (date) => date.substr(0, 10) +export const getDateOnly = (date) => { + if (typeof date === 'string') { + date.substr(0, 10) + } +} diff --git a/components/x-teaser-timeline/storybook/index.jsx b/components/x-teaser-timeline/storybook/index.jsx index b01aae2a1..1c2d8edf3 100644 --- a/components/x-teaser-timeline/storybook/index.jsx +++ b/components/x-teaser-timeline/storybook/index.jsx @@ -1,10 +1,9 @@ const { TeaserTimeline } = require('../dist/TeaserTimeline.cjs') import React from 'react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' const pkg = require('../package.json') +const { args, argTypes } = require('./timeline') const dependencies = { 'o-normalise': '^1.6.0', @@ -12,16 +11,11 @@ const dependencies = { 'o-teaser': '^2.3.1' } -const knobs = require('./knobs') - export default { - title: 'x-teaser-timeline', - decorators: [withKnobs] + title: 'x-teaser-timeline' } -export const Timeline = () => { - const { data, knobs: storyKnobs } = require('./timeline') - const props = createProps(data, storyKnobs, knobs) +export const Timeline = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -30,7 +24,10 @@ export const Timeline = () => { <link rel="stylesheet" href={`components/x-teaser-timeline/${pkg.style}`} /> </Helmet> )} - <TeaserTimeline {...props} /> + <TeaserTimeline {...args} /> </div> ) } + +Timeline.args = args +Timeline.argTypes = argTypes diff --git a/components/x-teaser-timeline/storybook/knobs.js b/components/x-teaser-timeline/storybook/knobs.js deleted file mode 100644 index 56cdc5055..000000000 --- a/components/x-teaser-timeline/storybook/knobs.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = (data, { number, select }) => { - return { - latestItemsTime() { - return select('Latest Items Time', { - None: '', - '2018-10-17T12:10:33.000Z': '2018-10-17T12:10:33.000Z' - }) - }, - customSlotPosition() { - return number('Custom Slot Position', data.customSlotPosition) - }, - customSlotContent() { - return select('Custom Slot Content', { - None: '', - Something: '---Custom slot content---' - }) - } - } -} diff --git a/components/x-teaser-timeline/storybook/timeline.js b/components/x-teaser-timeline/storybook/timeline.js index 7a200bbc6..83ee08b1d 100644 --- a/components/x-teaser-timeline/storybook/timeline.js +++ b/components/x-teaser-timeline/storybook/timeline.js @@ -1,6 +1,4 @@ -exports.title = 'Timeline' - -exports.data = { +exports.args = { items: require('./content-items.json'), timezoneOffset: -60, localTodayDate: '2018-10-17', @@ -9,7 +7,14 @@ exports.data = { customSlotPosition: 3 } -exports.knobs = Object.keys(exports.data) +exports.argTypes = { + latestItemsTime: { + control: { type: 'select', options: { None: '', '2018-10-17T12:10:33.000Z': '2018-10-17T12:10:33.000Z' } } + }, + customSlotContent: { + control: { type: 'select', options: { None: '', Something: '---Custom slot content---' } } + } +} // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> diff --git a/components/x-teaser/__fixtures__/article.json b/components/x-teaser/__fixtures__/article.json index 22486d022..fdcdb0a91 100644 --- a/components/x-teaser/__fixtures__/article.json +++ b/components/x-teaser/__fixtures__/article.json @@ -25,5 +25,11 @@ }, "indicators": { "isEditorsChoice": true - } + }, + "status": "", + "headshotTint": "", + "accessLevel": "free", + "theme": "", + "parentTheme": "", + "modifiers": "" } diff --git a/components/x-teaser/__fixtures__/content-package.json b/components/x-teaser/__fixtures__/content-package.json index fa85fafe5..d3c842a1f 100644 --- a/components/x-teaser/__fixtures__/content-package.json +++ b/components/x-teaser/__fixtures__/content-package.json @@ -22,5 +22,11 @@ "url": "http://prod-upp-image-read.ft.com/7e97f5b6-578d-11e8-b8b2-d6ceb45fa9d0", "width": 2048, "height": 1152 - } + }, + "status": "", + "headshotTint": "", + "accessLevel": "free", + "theme": "", + "parentTheme": "", + "modifiers": "" } diff --git a/components/x-teaser/__fixtures__/opinion.json b/components/x-teaser/__fixtures__/opinion.json index d455871bd..541dd1f10 100644 --- a/components/x-teaser/__fixtures__/opinion.json +++ b/components/x-teaser/__fixtures__/opinion.json @@ -27,5 +27,11 @@ "indicators": { "isOpinion": true, "isColumn": true - } + }, + "status": "", + "headshotTint": "", + "accessLevel": "free", + "theme": "", + "parentTheme": "", + "modifiers": "" } diff --git a/components/x-teaser/__fixtures__/package-item.json b/components/x-teaser/__fixtures__/package-item.json index cdb8a9985..59185d427 100644 --- a/components/x-teaser/__fixtures__/package-item.json +++ b/components/x-teaser/__fixtures__/package-item.json @@ -19,5 +19,11 @@ }, "indicators": { "isOpinion": true - } + }, + "status": "", + "headshotTint": "", + "accessLevel": "free", + "theme": "", + "parentTheme": "", + "modifiers": "" } diff --git a/components/x-teaser/__fixtures__/podcast.json b/components/x-teaser/__fixtures__/podcast.json index fcb7b98f2..734ec3d6a 100644 --- a/components/x-teaser/__fixtures__/podcast.json +++ b/components/x-teaser/__fixtures__/podcast.json @@ -12,7 +12,7 @@ "url": "#", "prefLabel": "Tech Tonic podcast" }, - "metaAltLink": null, + "metaAltLink": "", "image": { "url": "https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F5d1a54aa-f49b-11e8-ae55-df4bf40f9d0d?source=next&fit=scale-down&compression=best&width=240", "width": 2048, @@ -20,5 +20,11 @@ }, "indicators": { "isPodcast": true - } + }, + "status": "", + "headshotTint": "", + "accessLevel": "free", + "theme": "", + "parentTheme": "", + "modifiers": "" } diff --git a/components/x-teaser/__fixtures__/promoted.json b/components/x-teaser/__fixtures__/promoted.json index 3d1bb68ec..987c45072 100644 --- a/components/x-teaser/__fixtures__/promoted.json +++ b/components/x-teaser/__fixtures__/promoted.json @@ -10,5 +10,11 @@ "url": "https://tpc.googlesyndication.com/pagead/imgad?id=CICAgKCrm_3yahABGAEyCMx3RoLss603", "width": 700, "height": 394 - } + }, + "status": "", + "headshotTint": "", + "accessLevel": "free", + "theme": "", + "parentTheme": "", + "modifiers": "" } diff --git a/components/x-teaser/__fixtures__/top-story.json b/components/x-teaser/__fixtures__/top-story.json index 19f9efb56..d179f5011 100644 --- a/components/x-teaser/__fixtures__/top-story.json +++ b/components/x-teaser/__fixtures__/top-story.json @@ -43,5 +43,11 @@ "type": "video", "title": "PM speaks out after Presidents Club dinner" } - ] + ], + "status": "", + "headshotTint": "", + "accessLevel": "free", + "theme": "", + "parentTheme": "", + "modifiers": "" } diff --git a/components/x-teaser/storybook/argTypes.js b/components/x-teaser/storybook/argTypes.js new file mode 100644 index 000000000..228d16bbf --- /dev/null +++ b/components/x-teaser/storybook/argTypes.js @@ -0,0 +1,51 @@ +exports.argTypes = { + status: { + name: 'Live Blog Status', + control: { + type: 'select', + options: { + None: '', + 'Coming soon': 'comingsoon', + 'In progress': 'inprogress', + Closed: 'closed' + } + } + }, + imageSize: { + name: 'Image Size', + control: { type: 'select', options: ['XS', 'Small', 'Medium', 'Large', 'XL', 'XXL'] } + }, + headshotTint: { name: 'Headshot tint', control: { type: 'select', options: { Default: '' } } }, + accessLevel: { + name: 'Access level', + control: { type: 'select', options: ['free', 'registered', 'subscribed', 'premium'] } + }, + layout: { name: 'Layout', control: { type: 'select', options: ['small', 'large', 'hero', 'top-story'] } }, + theme: { + name: 'Theme', + control: { type: 'select', options: { None: '', Extra: 'extra-article', 'Special Report': 'highlight' } } + }, + parentTheme: { + name: 'Parent Theme', + control: { type: 'select', options: { None: '', Extra: 'extra-article', 'Special Report': 'highlight' } } + }, + modifiers: { + name: 'Modifiers', + control: { + type: 'select', + options: { + None: '', + 'Small stacked': 'stacked', + 'Small image on right': 'image-on-right', + 'Large portrait': 'large-portrait', + 'Large landscape': 'large-landscape', + 'Hero centre': 'centre', + 'Hero image': 'hero-image', + 'Top story landscape': 'landscape', + 'Top story big': 'big-story' + } + } + }, + publishedDate: { name: 'Published Date', control: { type: 'date' } }, + firstPublishedDate: { name: 'First Published Date', control: { type: 'date' } } +} diff --git a/components/x-teaser/storybook/article.js b/components/x-teaser/storybook/article.js index b2fac6bff..1b3d18b97 100644 --- a/components/x-teaser/storybook/article.js +++ b/components/x-teaser/storybook/article.js @@ -1,50 +1,6 @@ const { presets } = require('../') -exports.title = 'Article' - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/article.json'), presets.SmallHeavy) - -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = [ - 'id', - 'url', - 'type', - // Meta - 'showMeta', - 'metaPrefixText', - 'metaSuffixText', - 'metaLink', - 'dataTrackable', - // Title - 'showTitle', - 'title', - 'altTitle', - // Standfirst - 'showStandfirst', - 'standfirst', - 'altStandfirst', - // Status - 'showStatus', - 'publishedDate', - 'firstPublishedDate', - 'useRelativeTime', - 'status', - // Image - 'showImage', - 'image', - 'imageSize', - 'imageHighestQuality', - // Indicators - 'indicators', - // Context - 'headlineTesting', - // Variants - 'layout', - 'modifiers' -] +exports.args = Object.assign(require('../__fixtures__/article.json'), presets.SmallHeavy) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> diff --git a/components/x-teaser/storybook/content-package.js b/components/x-teaser/storybook/content-package.js index e5ace4a56..d57248a09 100644 --- a/components/x-teaser/storybook/content-package.js +++ b/components/x-teaser/storybook/content-package.js @@ -1,45 +1,9 @@ const { presets } = require('../') -exports.title = 'Content Package' - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/content-package.json'), presets.Hero, { +exports.args = Object.assign(require('../__fixtures__/content-package.json'), presets.Hero, { modifiers: 'centre' }) -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = [ - 'id', - 'url', - 'type', - // Meta - 'showMeta', - 'metaLink', - 'dataTrackable', - // Title - 'showTitle', - 'title', - // Standfirst - 'showStandfirst', - 'standfirst', - // Status - 'showStatus', - 'publishedDate', - 'firstPublishedDate', - 'useRelativeTime', - // Image - 'showImage', - 'image', - 'imageSize', - 'imageHighestQuality', - // Variants - 'layout', - 'theme', - 'modifiers' -] - // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index 4981ffbce..b41fc9c1a 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -1,9 +1,7 @@ const { Teaser } = require('../') import React from 'react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' const path = require('path') const pkg = require('../package.json') const name = path.basename(pkg.name) @@ -17,16 +15,13 @@ const dependencies = { 'o-video': '^6.0.0' } -const knobs = require('./knobs') +const { argTypes } = require('./argTypes') export default { - title: 'x-teaser', - decorators: [withKnobs] + title: 'x-teaser' } -export const Article = () => { - const { data, knobs: storyKnobs } = require('./article') - const props = createProps(data, storyKnobs, knobs) +export const Article = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -35,14 +30,15 @@ export const Article = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Teaser {...props} /> + <Teaser {...args} /> </div> ) } -export const Podcast = () => { - const { data, knobs: storyKnobs } = require('./podcast') - const props = createProps(data, storyKnobs, knobs) +Article.args = require('./article').args +Article.argTypes = argTypes + +export const Podcast = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -51,14 +47,14 @@ export const Podcast = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Teaser {...props} /> + <Teaser {...args} /> </div> ) } +Podcast.args = require('./podcast').args +Podcast.argTypes = argTypes -export const Opinion = () => { - const { data, knobs: storyKnobs } = require('./opinion') - const props = createProps(data, storyKnobs, knobs) +export const Opinion = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -67,14 +63,14 @@ export const Opinion = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Teaser {...props} /> + <Teaser {...args} /> </div> ) } +Opinion.args = require('./opinion').args +Opinion.argTypes = argTypes -export const ContentPackage = () => { - const { data, knobs: storyKnobs } = require('./content-package') - const props = createProps(data, storyKnobs, knobs) +export const ContentPackage = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -83,7 +79,7 @@ export const ContentPackage = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Teaser {...props} /> + <Teaser {...args} /> </div> ) } @@ -91,10 +87,10 @@ export const ContentPackage = () => { ContentPackage.story = { name: 'ContentPackage' } +ContentPackage.args = require('./content-package').args +ContentPackage.argTypes = argTypes -export const PackageItem = () => { - const { data, knobs: storyKnobs } = require('./package-item') - const props = createProps(data, storyKnobs, knobs) +export const PackageItem = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -103,7 +99,7 @@ export const PackageItem = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Teaser {...props} /> + <Teaser {...args} /> </div> ) } @@ -111,10 +107,10 @@ export const PackageItem = () => { PackageItem.story = { name: 'PackageItem' } +PackageItem.args = require('./package-item').args +PackageItem.argTypes = argTypes -export const Promoted = () => { - const { data, knobs: storyKnobs } = require('./promoted') - const props = createProps(data, storyKnobs, knobs) +export const Promoted = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -123,14 +119,15 @@ export const Promoted = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Teaser {...props} /> + <Teaser {...args} /> </div> ) } -export const TopStory = () => { - const { data, knobs: storyKnobs } = require('./top-story') - const props = createProps(data, storyKnobs, knobs) +Promoted.args = require('./promoted').args +Promoted.argTypes = argTypes + +export const TopStory = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -139,7 +136,7 @@ export const TopStory = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Teaser {...props} /> + <Teaser {...args} /> </div> ) } @@ -147,10 +144,10 @@ export const TopStory = () => { TopStory.story = { name: 'TopStory' } +TopStory.args = require('./top-story').args +TopStory.argTypes = argTypes -export const Video = () => { - const { data, knobs: storyKnobs } = require('./video') - const props = createProps(data, storyKnobs, knobs) +export const Video = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -159,7 +156,9 @@ export const Video = () => { <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> </Helmet> )} - <Teaser {...props} /> + <Teaser {...args} /> </div> ) } +Video.args = require('./video').args +Video.argTypes = argTypes diff --git a/components/x-teaser/storybook/knobs.js b/components/x-teaser/storybook/knobs.js deleted file mode 100644 index 92cb8cf42..000000000 --- a/components/x-teaser/storybook/knobs.js +++ /dev/null @@ -1,253 +0,0 @@ -// To ensure that component stories do not need to depend on Storybook themselves we return a -// function that may be passed the required dependencies. -module.exports = (data, { object, text, number, boolean, date, select }) => { - const Groups = { - Meta: 'Meta', - Title: 'Title', - Standfirst: 'Standfirst', - Status: 'Status', - Image: 'Image', - Headshot: 'Headshot', - Video: 'Video', - RelatedLinks: 'Related Links', - Indicators: 'Indicators', - Context: 'Context', - Variant: 'Variant' - } - - const Features = { - showMeta() { - return boolean('Show meta', data.showMeta, Groups.Meta) - }, - showTitle() { - return boolean('Show title', data.showTitle, Groups.Title) - }, - showStandfirst() { - return boolean('Show standfirst', data.showStandfirst, Groups.Standfirst) - }, - showStatus() { - return boolean('Show status', data.showStatus, Groups.Status) - }, - showImage() { - return boolean('Show image', data.showImage, Groups.Image) - }, - showHeadshot() { - return boolean('Show headshot', data.showHeadshot, Groups.Headshot) - }, - showVideo() { - return boolean('Show video', data.showVideo, Groups.Video) - }, - showRelatedLinks() { - return boolean('Show related links', data.showRelatedLinks, Groups.RelatedLinks) - } - } - - const Meta = { - metaPrefixText() { - return text('Meta prefix text', data.metaPrefixText, Groups.Meta) - }, - metaSuffixText() { - return text('Meta suffix text', data.metaSuffixText, Groups.Meta) - }, - metaLink() { - return { - url: data.metaLink.url, - prefLabel: text('Meta link', data.metaLink.prefLabel, Groups.Meta) - } - }, - metaAltLink() { - return { - url: data.metaAltLink.url, - prefLabel: text('Alt meta link', data.metaAltLink.prefLabel, Groups.Meta) - } - }, - promotedPrefixText() { - return text('Promoted prefix text', data.promotedPrefixText, Groups.Meta) - }, - promotedSuffixText() { - return text('Promoted suffix text', data.promotedSuffixText, Groups.Meta) - }, - dataTrackable() { - return text('Tracking data', data.dataTrackable, Groups.Meta) - } - } - - const Title = { - title() { - return text('Title', data.title, Groups.Title) - }, - altTitle() { - return text('Alt title', data.altTitle, Groups.Title) - } - } - - const Standfirst = { - standfirst() { - return text('Standfirst', data.standfirst, Groups.Standfirst) - }, - altStandfirst() { - return text('Alt standfirst', data.altStandfirst, Groups.Standfirst) - } - } - - const Status = { - publishedDate() { - return date('Published date', new Date(data.publishedDate), Groups.Status) - }, - firstPublishedDate() { - return date('First published date', new Date(data.firstPublishedDate), Groups.Status) - }, - useRelativeTime() { - return boolean('Use relative time', data.useRelativeTime, Groups.Status) - }, - status() { - return select( - 'Live blog status', - { - None: '', - 'Coming soon': 'comingsoon', - 'In progress': 'inprogress', - Closed: 'closed' - }, - '', - Groups.Status - ) - } - } - - const Image = { - image() { - return { - url: text('Image URL', data.image.url, Groups.Image), - width: number('Image width', data.image.width, {}, Groups.Image), - height: number('Image height', data.image.height, {}, Groups.Image) - } - }, - imageSize() { - return select( - 'Image size', - ['XS', 'Small', 'Medium', 'Large', 'XL', 'XXL'], - data.imageSize, - Groups.Image - ) - }, - imageHighestQuality() { - return boolean('Image hi-quality', data.imageHighestQuality, Groups.Image) - } - } - - const Headshot = { - headshot() { - return text('Headshot', data.headshot, Groups.Headshot) - }, - headshotTint() { - return select('Headshot tint', { Default: '' }, 'Default', Groups.Headshot) - } - } - - const Video = { - video() { - return { - url: text('Video URL', data.video.url, Groups.Video), - width: number('Video width', data.video.width, {}, Groups.Video), - height: number('Video height', data.video.height, {}, Groups.Video), - mediaType: data.video.mediaType, - codec: data.video.codec - } - } - } - - const RelatedLinks = { - relatedLinks() { - return object('Related links', data.relatedLinks, Groups.RelatedLinks) - } - } - - const Indicators = { - indicators() { - return { - accessLevel: select( - 'Access level', - ['free', 'registered', 'subscribed', 'premium'], - data.indicators.accessLevel || 'free', - Groups.Indicators - ), - isOpinion: boolean('Is opinion', data.indicators.isOpinion, Groups.Indicators), - isColumn: boolean('Is column', data.indicators.isColumn, Groups.Indicators), - isPodcast: boolean('Is podcast', data.indicators.isPodcast, Groups.Indicators), - isEditorsChoice: boolean("Is editor's choice", data.indicators.isEditorsChoice, Groups.Indicators), - isExclusive: boolean('Is exclusive', data.indicators.isExclusive, Groups.Indicators), - isScoop: boolean('Is scoop', data.indicators.isScoop, Groups.Indicators) - } - } - } - - const Context = { - headlineTesting() { - return boolean('Headline testing', false, Groups.Context) - }, - parentId() { - return text('Parent ID', data.context.parentId, Groups.Context) - }, - parentLabel() { - return text('Parent Label', data.context.parentLabel, Groups.Context) - } - } - - const Variant = { - layout() { - return select('Layout', ['small', 'large', 'hero', 'top-story'], data.layout, Groups.Variant) - }, - theme() { - return select( - 'Theme', - { None: '', Extra: 'extra-article', 'Special Report': 'highlight' }, - data.theme, - Groups.Variant - ) - }, - parentTheme() { - return select( - 'Parent theme', - { None: '', Extra: 'extra-article', 'Special Report': 'highlight' }, - data.parentTheme, - Groups.Variant - ) - }, - modifiers() { - return select( - 'Modifiers', - { - // Currently no support for optgroups or multiple selections - None: '', - 'Small stacked': 'stacked', - 'Small image on right': 'image-on-right', - 'Large portrait': 'large-portrait', - 'Large landscape': 'large-landscape', - 'Hero centre': 'centre', - 'Hero image': 'hero-image', - 'Top story landscape': 'landscape', - 'Top story big': 'big-story' - }, - data.modifiers || '', - Groups.Variant - ) - } - } - - return Object.assign( - {}, - Features, - Meta, - Title, - Standfirst, - Status, - Video, - Headshot, - Image, - RelatedLinks, - Indicators, - Context, - Variant - ) -} diff --git a/components/x-teaser/storybook/opinion.js b/components/x-teaser/storybook/opinion.js index 8cfa62d53..1448d1a5b 100644 --- a/components/x-teaser/storybook/opinion.js +++ b/components/x-teaser/storybook/opinion.js @@ -1,52 +1,9 @@ const { presets } = require('../') -exports.title = 'Opinion Piece' - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/opinion.json'), presets.SmallHeavy, { +exports.args = Object.assign(require('../__fixtures__/opinion.json'), presets.SmallHeavy, { showHeadshot: true }) -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = [ - 'id', - 'url', - 'type', - // Meta - 'showMeta', - 'metaPrefixText', - 'metaSuffixText', - 'metaLink', - 'dataTrackable', - // Title - 'showTitle', - 'title', - // Standfirst - 'showStandfirst', - 'standfirst', - // Status - 'showStatus', - 'publishedDate', - 'firstPublishedDate', - 'useRelativeTime', - 'status', - // Headshot - 'showHeadshot', - 'headshot', - // Image - 'showImage', - 'image', - 'imageSize', - 'imageHighestQuality', - // Variants - 'layout', - 'modifiers', - // Indicators - 'indicators' -] - // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module diff --git a/components/x-teaser/storybook/package-item.js b/components/x-teaser/storybook/package-item.js index 4b65f7505..1bb78775a 100644 --- a/components/x-teaser/storybook/package-item.js +++ b/components/x-teaser/storybook/package-item.js @@ -1,51 +1,10 @@ const { presets } = require('../') -exports.title = 'Package item' - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/package-item.json'), presets.Hero, { +exports.args = Object.assign(require('../__fixtures__/package-item.json'), presets.Hero, { parentTheme: 'extra-article', modifiers: 'centre' }) -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = [ - 'id', - 'url', - 'type', - // Meta - 'showMeta', - 'metaPrefixText', - 'metaSuffixText', - 'metaLink', - 'dataTrackable', - // Title - 'showTitle', - 'title', - // Standfirst - 'showStandfirst', - 'standfirst', - // Status - 'showStatus', - 'publishedDate', - 'firstPublishedDate', - 'useRelativeTime', - // Image - 'showImage', - 'image', - 'imageSize', - 'imageHighestQuality', - // Indicators - 'indicators', - // Variants - 'layout', - 'theme', - 'parentTheme', - 'modifiers' -] - // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> exports.m = module diff --git a/components/x-teaser/storybook/podcast.js b/components/x-teaser/storybook/podcast.js index 30075ff06..ff5ba6fa4 100644 --- a/components/x-teaser/storybook/podcast.js +++ b/components/x-teaser/storybook/podcast.js @@ -1,50 +1,6 @@ const { presets } = require('../') -exports.title = 'Podcast' - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/podcast.json'), presets.SmallHeavy) - -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = [ - 'id', - 'url', - 'type', - // Meta - 'showMeta', - 'metaPrefixText', - 'metaSuffixText', - 'metaLink', - 'dataTrackable', - // Title - 'showTitle', - 'title', - 'altTitle', - // Standfirst - 'showStandfirst', - 'standfirst', - 'altStandfirst', - // Status - 'showStatus', - 'publishedDate', - 'firstPublishedDate', - 'useRelativeTime', - 'status', - // Image - 'showImage', - 'image', - 'imageSize', - 'imageHighestQuality', - // Indicators - 'indicators', - // Context - 'headlineTesting', - // Variants - 'layout', - 'modifiers' -] +exports.args = Object.assign(require('../__fixtures__/podcast.json'), presets.SmallHeavy) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> diff --git a/components/x-teaser/storybook/promoted.js b/components/x-teaser/storybook/promoted.js index 00d54d760..54333da97 100644 --- a/components/x-teaser/storybook/promoted.js +++ b/components/x-teaser/storybook/promoted.js @@ -1,37 +1,6 @@ const { presets } = require('../') -exports.title = 'Paid Post' - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/promoted.json'), presets.SmallHeavy) - -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = [ - 'id', - 'url', - 'type', - // Meta - 'showMeta', - 'promotedPrefixText', - 'promotedSuffixText', - 'dataTrackable', - // Title - 'showTitle', - 'title', - // Standfirst - 'showStandfirst', - 'standfirst', - // Image - 'showImage', - 'image', - 'imageSize', - 'imageHighestQuality', - // Variants - 'layout', - 'modifiers' -] +exports.args = Object.assign(require('../__fixtures__/promoted.json'), presets.SmallHeavy) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> diff --git a/components/x-teaser/storybook/top-story.js b/components/x-teaser/storybook/top-story.js index 372496a0a..73a882196 100644 --- a/components/x-teaser/storybook/top-story.js +++ b/components/x-teaser/storybook/top-story.js @@ -1,46 +1,6 @@ const { presets } = require('../') -exports.title = 'Top Story' - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/top-story.json'), presets.TopStoryLandscape) - -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = [ - 'id', - 'url', - 'type', - // Meta - 'showMeta', - 'metaPrefixText', - 'metaSuffixText', - 'metaLink', - 'dataTrackable', - // Title - 'showTitle', - 'title', - // Standfirst - 'showStandfirst', - 'standfirst', - // Status - 'showStatus', - 'publishedDate', - 'firstPublishedDate', - 'useRelativeTime', - // Image - 'showImage', - 'image', - 'imageSize', - 'imageHighestQuality', - // Related - 'showRelatedLinks', - 'relatedLinks', - // Variants - 'layout', - 'modifiers' -] +exports.args = Object.assign(require('../__fixtures__/top-story.json'), presets.TopStoryLandscape) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> diff --git a/components/x-teaser/storybook/video.js b/components/x-teaser/storybook/video.js index 42171e578..1781fe730 100644 --- a/components/x-teaser/storybook/video.js +++ b/components/x-teaser/storybook/video.js @@ -1,44 +1,6 @@ const { presets } = require('../') -exports.title = 'Video' - -// This data will provide defaults for the Knobs defined below and used -// to render examples in the documentation site. -exports.data = Object.assign(require('../__fixtures__/video.json'), presets.HeroVideo) - -// A list of properties to pass to the component when rendered in Storybook. If a Knob -// exists for the property then it will be editable with the default as defined above. -exports.knobs = [ - 'id', - 'url', - 'type', - 'systemCode', - // Meta - 'showMeta', - 'metaPrefixText', - 'metaSuffixText', - 'metaLink', - 'dataTrackable', - // Title - 'showTitle', - 'title', - // Status - 'showStatus', - 'publishedDate', - 'firstPublishedDate', - 'useRelativeTime', - // Image - 'showImage', - 'image', - 'imageSize', - 'imageHighestQuality', - // Video - 'showVideo', - 'video', - // Variants - 'layout', - 'modifiers' -] +exports.args = Object.assign(require('../__fixtures__/video.json'), presets.HeroVideo) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> diff --git a/package.json b/package.json index 21f9d899d..4a7a3a084 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,7 @@ "devDependencies": { "@babel/core": "^7.4.5", "@financial-times/athloi": "^1.0.0-beta.26", - "@storybook/addon-knobs": "^6.0.28", - "@storybook/addon-viewport": "^6.0.28", + "@storybook/addon-controls": "^6.0.28", "@storybook/react": "^6.0.28", "@types/jest": "26.0.0", "@typescript-eslint/parser": "^3.0.0", @@ -68,5 +67,6 @@ "components/*", "packages/*", "tools/*" - ] + ], + "dependencies": {} } From 1b47b4e78e312f7ab20cd91a70d5324186610fdb Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Thu, 19 Nov 2020 12:51:34 +0000 Subject: [PATCH 606/760] storybook - added fetch-mock --- components/x-gift-article/storybook/error-response.js | 1 + components/x-gift-article/storybook/free-article.js | 1 + components/x-gift-article/storybook/index.jsx | 8 +++++++- components/x-gift-article/storybook/native-share.js | 1 + components/x-gift-article/storybook/with-gift-credits.js | 1 + components/x-gift-article/storybook/with-gift-link.js | 2 +- .../x-gift-article/storybook/without-gift-credits.js | 1 + components/x-privacy-manager/storybook/data.js | 9 ++++----- components/x-privacy-manager/storybook/index.jsx | 7 ++++++- 9 files changed, 23 insertions(+), 8 deletions(-) diff --git a/components/x-gift-article/storybook/error-response.js b/components/x-gift-article/storybook/error-response.js index 19674241a..ce3c250c1 100644 --- a/components/x-gift-article/storybook/error-response.js +++ b/components/x-gift-article/storybook/error-response.js @@ -17,6 +17,7 @@ exports.m = module exports.fetchMock = (fetchMock) => { fetchMock + .restore() .get('/article/gift-credits', { throw: new Error('bad membership api') }) diff --git a/components/x-gift-article/storybook/free-article.js b/components/x-gift-article/storybook/free-article.js index 1bcfd3d11..f4bb729ac 100644 --- a/components/x-gift-article/storybook/free-article.js +++ b/components/x-gift-article/storybook/free-article.js @@ -17,6 +17,7 @@ exports.m = module exports.fetchMock = (fetchMock) => { fetchMock + .restore() .get('/article/gift-credits', { credits: { allowance: 20, diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index b8f7c6c61..44e00dbef 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -1,4 +1,5 @@ const { GiftArticle } = require('../dist/GiftArticle.cjs') +import fetchMock from 'fetch-mock' import React from 'react' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' @@ -13,6 +14,7 @@ export default { } export const WithGiftCredits = (args) => { + require('./with-gift-credits').fetchMock(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -25,13 +27,13 @@ export const WithGiftCredits = (args) => { </div> ) } - WithGiftCredits.story = { name: 'With gift credits' } WithGiftCredits.args = require('./with-gift-credits').args export const WithoutGiftCredits = (args) => { + require('./without-gift-credits').fetchMock(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -51,6 +53,7 @@ WithoutGiftCredits.story = { WithoutGiftCredits.args = require('./without-gift-credits').args export const WithGiftLink = (args) => { + require('./with-gift-link').fetchMock(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -70,6 +73,7 @@ WithGiftLink.story = { WithGiftLink.args = require('./with-gift-link').args export const FreeArticle = (args) => { + require('./free-article').fetchMock(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -89,6 +93,7 @@ FreeArticle.story = { FreeArticle.args = require('./free-article').args export const NativeShare = (args) => { + require('./native-share').fetchMock(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -108,6 +113,7 @@ NativeShare.story = { NativeShare.args = require('./native-share').args export const ErrorResponse = (args) => { + require('./error-response').fetchMock(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} diff --git a/components/x-gift-article/storybook/native-share.js b/components/x-gift-article/storybook/native-share.js index be1f2967d..679d714fe 100644 --- a/components/x-gift-article/storybook/native-share.js +++ b/components/x-gift-article/storybook/native-share.js @@ -21,6 +21,7 @@ exports.m = module exports.fetchMock = (fetchMock) => { fetchMock + .restore() .get('/article/gift-credits', { credits: { allowance: 20, diff --git a/components/x-gift-article/storybook/with-gift-credits.js b/components/x-gift-article/storybook/with-gift-credits.js index d520e3489..371888035 100644 --- a/components/x-gift-article/storybook/with-gift-credits.js +++ b/components/x-gift-article/storybook/with-gift-credits.js @@ -21,6 +21,7 @@ exports.m = module exports.fetchMock = (fetchMock) => { fetchMock + .restore() .get('/article/gift-credits', { credits: { allowance: 20, diff --git a/components/x-gift-article/storybook/with-gift-link.js b/components/x-gift-article/storybook/with-gift-link.js index cfb57a86a..1c8ad313f 100644 --- a/components/x-gift-article/storybook/with-gift-link.js +++ b/components/x-gift-article/storybook/with-gift-link.js @@ -21,7 +21,7 @@ exports.args = { exports.m = module exports.fetchMock = (fetchMock) => { - fetchMock.get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + fetchMock.restore().get(`/article/gift-link/${encodeURIComponent(articleId)}`, { redemptionUrl: articleUrlRedeemed, remainingAllowance: 1 }) diff --git a/components/x-gift-article/storybook/without-gift-credits.js b/components/x-gift-article/storybook/without-gift-credits.js index cea0a4069..1b9f9dd67 100644 --- a/components/x-gift-article/storybook/without-gift-credits.js +++ b/components/x-gift-article/storybook/without-gift-credits.js @@ -17,6 +17,7 @@ exports.m = module exports.fetchMock = (fetchMock) => { fetchMock + .restore() .get('/article/gift-credits', { credits: { allowance: 20, diff --git a/components/x-privacy-manager/storybook/data.js b/components/x-privacy-manager/storybook/data.js index 2bfc1ec4a..6cd708b5d 100644 --- a/components/x-privacy-manager/storybook/data.js +++ b/components/x-privacy-manager/storybook/data.js @@ -45,10 +45,9 @@ const defaultArgTypes = { consent: { control: { type: 'boolean' }, name: 'consent' } } -const getFetchMock = (status = 200, options = {}) => (fetchMock) => { - fetchMock.mock(CONSENT_API, status, { - delay: 1000, - ...options +const fetchMock = (fetchMock, status = 200) => { + fetchMock.restore().mock(CONSENT_API, status, { + delay: 1000 }) } @@ -56,5 +55,5 @@ module.exports = { CONSENT_API, defaultArgs, defaultArgTypes, - getFetchMock + fetchMock } diff --git a/components/x-privacy-manager/storybook/index.jsx b/components/x-privacy-manager/storybook/index.jsx index 4b2a97cf1..9fdecd657 100644 --- a/components/x-privacy-manager/storybook/index.jsx +++ b/components/x-privacy-manager/storybook/index.jsx @@ -1,5 +1,6 @@ const { PrivacyManager } = require('../src/privacy-manager') -const { defaultArgs, defaultArgTypes } = require('./data') +const { defaultArgs, defaultArgTypes, fetchMock: privacyFM } = require('./data') +import fetchMock from 'fetch-mock' import React from 'react' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' @@ -16,6 +17,7 @@ export default { } export const ConsentIndeterminate = (args) => { + privacyFM(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -37,6 +39,7 @@ ConsentIndeterminate.args = defaultArgs ConsentIndeterminate.argTypes = defaultArgTypes export const ConsentAccepted = (args) => { + privacyFM(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -60,6 +63,7 @@ ConsentAccepted.args = { ConsentAccepted.argTypes = defaultArgTypes export const ConsentBlocked = (args) => { + privacyFM(fetchMock) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -83,6 +87,7 @@ ConsentBlocked.args = { ConsentBlocked.argTypes = defaultArgTypes export const SaveFailed = (args) => { + privacyFM(fetchMock, 500) return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} From ff1c5bb83be88c7e6dd5aaa42d3aa787c321f52d Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Thu, 19 Nov 2020 13:17:45 +0000 Subject: [PATCH 607/760] restored getDateOnly --- components/x-teaser-timeline/src/lib/date.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/x-teaser-timeline/src/lib/date.js b/components/x-teaser-timeline/src/lib/date.js index 2b8fd7d40..70bf95598 100644 --- a/components/x-teaser-timeline/src/lib/date.js +++ b/components/x-teaser-timeline/src/lib/date.js @@ -48,8 +48,4 @@ export const splitLatestEarlier = (items, splitDate) => { return { latestItems, earlierItems } } -export const getDateOnly = (date) => { - if (typeof date === 'string') { - date.substr(0, 10) - } -} +export const getDateOnly = (date) => date.substr(0, 10) From 57e5eca0b3e5eb32310d37e97b74ed3b51f41c26 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Thu, 19 Nov 2020 17:52:51 +0000 Subject: [PATCH 608/760] fixed path on teaserTimeline --- components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx index 53e04e9e0..c20ee752f 100644 --- a/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx +++ b/components/x-teaser-timeline/__tests__/TeaserTimeline.test.jsx @@ -1,7 +1,7 @@ const renderer = require('react-test-renderer') const { h } = require('@financial-times/x-engine') const { shallow } = require('@financial-times/x-test-utils/enzyme') -const contentItems = require('../stories/content-items.json') +const contentItems = require('../storybook/content-items.json') const { TeaserTimeline } = require('../') From 3a50ad1690700fb063800b9195dbf2d2b79d5553 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Thu, 19 Nov 2020 18:54:02 +0000 Subject: [PATCH 609/760] change stories to storybook in snapshots.test --- __tests__/snapshots.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/snapshots.test.js b/__tests__/snapshots.test.js index ed3c6b42a..089f5f892 100644 --- a/__tests__/snapshots.test.js +++ b/__tests__/snapshots.test.js @@ -12,7 +12,7 @@ const packageDirs = glob.sync(packagesGlob) for (const pkg of packageDirs) { const pkgDir = path.resolve(pkg) - const storiesDir = path.resolve(pkgDir, 'stories') + const storiesDir = path.resolve(pkgDir, 'storybook') if (fs.existsSync(storiesDir)) { const { package: pkg, stories, component } = require(storiesDir) From dd3cf726c953d9d6aaf9bb12187ed782ffc27858 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Fri, 20 Nov 2020 13:51:51 +0000 Subject: [PATCH 610/760] removed snapshot tests for stories --- .../__snapshots__/snapshots.test.js.snap | 3517 ----------------- __tests__/snapshots.test.js | 32 - 2 files changed, 3549 deletions(-) delete mode 100644 __tests__/__snapshots__/snapshots.test.js.snap delete mode 100644 __tests__/snapshots.test.js diff --git a/__tests__/__snapshots__/snapshots.test.js.snap b/__tests__/__snapshots__/snapshots.test.js.snap deleted file mode 100644 index ae270cfd9..000000000 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ /dev/null @@ -1,3517 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`@financial-times/x-gift-article renders a default Free article x-gift-article 1`] = ` -<div - className="GiftArticle_container__2eZcF" -> - <form - className="GiftArticle_share-form__flrZ_" - name="gift-form" - > - <div - arialabelledby="gift-article-title" - role="group" - > - <div - className="GiftArticle_title__1i_Hv" - id="gift-article-title" - > - Share this article (free) - </div> - <div - className="js-gift-article__url-section GiftArticle_url-section__ISfN-" - data-section-id="nonGiftLink" - data-trackable="nonGiftLink" - > - <span - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" - > - <input - aria-label="Gift article shareable link" - className="GiftArticle_url-input__1vM0-" - disabled={false} - name="non-gift-link" - readOnly={true} - type="text" - value="https://www.ft.com/content/blahblahblah?shareType=nongift" - /> - </span> - <div - className="GiftArticle_message__3UjVE" - > - This article is currently - <strong> - free - </strong> - for anyone to read - </div> - <div - className="GiftArticle_buttons__1zOQK" - > - <a - className="GiftArticle_buttonBaseStyle__2ViHW" - href="mailto:?subject=Title%20Title%20Title%20Title&body=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah%3FshareType%3Dnongift" - onClick={[Function]} - rel="noopener noreferrer" - target="_blank" - > - Email link - <span - className="GiftArticle_visually-hidden__Okd-v" - > - to Share this article - </span> - </a> - </div> - </div> - </div> - </form> -</div> -`; - -exports[`@financial-times/x-gift-article renders a default With a bad response from membership APIs x-gift-article 1`] = ` -<div - className="GiftArticle_container__2eZcF" -> - <form - className="GiftArticle_share-form__flrZ_" - name="gift-form" - > - <div - arialabelledby="gift-article-title" - role="group" - > - <div - className="GiftArticle_title__1i_Hv" - id="gift-article-title" - > - Share this article (unable to fetch credits) - </div> - <div - aria-labelledby="article-share-options" - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" - role="group" - > - <span - className="GiftArticle_share-option-title__1CFAK" - id="article-share-options" - > - Article share options - </span> - <label - htmlFor="giftLink" - > - <input - checked={true} - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - anyone - </strong> - (uses 1 gift credit) - </span> - </label> - <label - htmlFor="nonGiftLink" - > - <input - checked={false} - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - other FT subscribers - </strong> - </span> - </label> - </div> - <div - className="js-gift-article__url-section GiftArticle_url-section__ISfN-" - data-section-id="giftLink" - data-trackable="giftLink" - > - <span - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" - > - <input - aria-label="Gift article shareable link" - className="GiftArticle_url-input__1vM0-" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> - </span> - <div - className="GiftArticle_message__3UjVE" - > - You have - - <strong> - gift article - credits - </strong> - - left this month - </div> - <div - className="GiftArticle_buttons__1zOQK" - > - <button - className="GiftArticle_buttonBaseStyle__2ViHW" - disabled={true} - onClick={[Function]} - type="button" - > - Create gift link - </button> - </div> - </div> - </div> - </form> -</div> -`; - -exports[`@financial-times/x-gift-article renders a default With gift credits x-gift-article 1`] = ` -<div - className="GiftArticle_container__2eZcF" -> - <form - className="GiftArticle_share-form__flrZ_" - name="gift-form" - > - <div - arialabelledby="gift-article-title" - role="group" - > - <div - className="GiftArticle_title__1i_Hv" - id="gift-article-title" - > - Share this article (with credit) - </div> - <div - aria-labelledby="article-share-options" - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" - role="group" - > - <span - className="GiftArticle_share-option-title__1CFAK" - id="article-share-options" - > - Article share options - </span> - <label - htmlFor="giftLink" - > - <input - checked={true} - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - anyone - </strong> - (uses 1 gift credit) - </span> - </label> - <label - htmlFor="nonGiftLink" - > - <input - checked={false} - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - other FT subscribers - </strong> - </span> - </label> - </div> - <div - className="js-gift-article__url-section GiftArticle_url-section__ISfN-" - data-section-id="giftLink" - data-trackable="giftLink" - > - <span - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" - > - <input - aria-label="Gift article shareable link" - className="GiftArticle_url-input__1vM0-" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> - </span> - <div - className="GiftArticle_message__3UjVE" - > - You have - - <strong> - gift article - credits - </strong> - - left this month - </div> - <div - className="GiftArticle_buttons__1zOQK" - > - <button - className="GiftArticle_buttonBaseStyle__2ViHW" - disabled={true} - onClick={[Function]} - type="button" - > - Create gift link - </button> - </div> - </div> - </div> - </form> - <div - className="MobileShareButtons_container__3eAtc" - > - <div - className="GiftArticle_title__1i_Hv" - id="gift-article-title" - > - Share on Social - </div> - <div - className="MobileShareButtons_container-inner__1Bpmj" - > - <span - className="MobileShareButtons_button__17W-1" - data-share="facebook" - > - <a - className="MobileShareButtons_facebook__1ji2o" - data-trackable="facebook" - href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" - rel="noopener" - > - Facebook - <span - className="MobileShareButtons_hidden-button-text__3Uqb1" - > - (opens new window) - </span> - </a> - </span> - <span - className="MobileShareButtons_button__17W-1" - data-share="twitter" - > - <a - className="MobileShareButtons_twitter__1QRsw" - data-trackable="twitter" - href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" - rel="noopener" - > - Twitter - <span - className="MobileShareButtons_hidden-button-text__3Uqb1" - > - (opens new window) - </span> - </a> - </span> - <span - className="MobileShareButtons_button__17W-1" - data-share="linkedin" - > - <a - className="MobileShareButtons_linkedin__1-2-Y" - data-trackable="linkedin" - href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" - rel="noopener" - > - LinkedIn - <span - className="MobileShareButtons_hidden-button-text__3Uqb1" - > - (opens new window) - </span> - </a> - </span> - <span - className="MobileShareButtons_button__17W-1" - data-share="whatsapp" - > - <a - className="MobileShareButtons_whatsapp__16VoZ" - data-trackable="whatsapp" - href="whatsapp://send?text=Title%20Title%20Title%20Title%20-%20https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" - rel="noopener" - > - Whatsapp - <span - className="MobileShareButtons_hidden-button-text__3Uqb1" - > - (opens new window) - </span> - </a> - </span> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-gift-article renders a default With gift link x-gift-article 1`] = ` -<div - className="GiftArticle_container__2eZcF" -> - <form - className="GiftArticle_share-form__flrZ_" - name="gift-form" - > - <div - arialabelledby="gift-article-title" - role="group" - > - <div - className="GiftArticle_title__1i_Hv" - id="gift-article-title" - > - Share this article (with gift link) - </div> - <div - aria-labelledby="article-share-options" - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" - role="group" - > - <span - className="GiftArticle_share-option-title__1CFAK" - id="article-share-options" - > - Article share options - </span> - <label - htmlFor="giftLink" - > - <input - checked={true} - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - anyone - </strong> - (uses 1 gift credit) - </span> - </label> - <label - htmlFor="nonGiftLink" - > - <input - checked={false} - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - other FT subscribers - </strong> - </span> - </label> - </div> - <div - className="js-gift-article__url-section GiftArticle_url-section__ISfN-" - data-section-id="giftLink" - data-trackable="giftLink" - > - <span - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" - > - <input - aria-label="Gift article shareable link" - className="GiftArticle_url-input__1vM0-" - disabled={false} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> - </span> - <div - className="GiftArticle_message__3UjVE" - > - This link can be opened up to - 3 - times and is valid for 90 days - </div> - <div - className="GiftArticle_buttons__1zOQK" - > - <a - className="GiftArticle_buttonBaseStyle__2ViHW" - onClick={[Function]} - rel="noopener noreferrer" - target="_blank" - > - Email link - <span - className="GiftArticle_visually-hidden__Okd-v" - > - to Share this article - </span> - </a> - </div> - </div> - </div> - </form> - <div - className="MobileShareButtons_container__3eAtc" - > - <div - className="GiftArticle_title__1i_Hv" - id="gift-article-title" - > - Share on Social - </div> - <div - className="MobileShareButtons_container-inner__1Bpmj" - > - <span - className="MobileShareButtons_button__17W-1" - data-share="facebook" - > - <a - className="MobileShareButtons_facebook__1ji2o" - data-trackable="facebook" - href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&t=Title%20Title%20Title%20Title" - rel="noopener" - > - Facebook - <span - className="MobileShareButtons_hidden-button-text__3Uqb1" - > - (opens new window) - </span> - </a> - </span> - <span - className="MobileShareButtons_button__17W-1" - data-share="twitter" - > - <a - className="MobileShareButtons_twitter__1QRsw" - data-trackable="twitter" - href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&text=Title%20Title%20Title%20Title&via=financialtimes" - rel="noopener" - > - Twitter - <span - className="MobileShareButtons_hidden-button-text__3Uqb1" - > - (opens new window) - </span> - </a> - </span> - <span - className="MobileShareButtons_button__17W-1" - data-share="linkedin" - > - <a - className="MobileShareButtons_linkedin__1-2-Y" - data-trackable="linkedin" - href="http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah&title=Title%20Title%20Title%20Title&source=Financial+Times" - rel="noopener" - > - LinkedIn - <span - className="MobileShareButtons_hidden-button-text__3Uqb1" - > - (opens new window) - </span> - </a> - </span> - <span - className="MobileShareButtons_button__17W-1" - data-share="whatsapp" - > - <a - className="MobileShareButtons_whatsapp__16VoZ" - data-trackable="whatsapp" - href="whatsapp://send?text=Title%20Title%20Title%20Title%20-%20https%3A%2F%2Fwww.ft.com%2Fcontent%2Fblahblahblah" - rel="noopener" - > - Whatsapp - <span - className="MobileShareButtons_hidden-button-text__3Uqb1" - > - (opens new window) - </span> - </a> - </span> - </div> - </div> -</div> -`; - -exports[`@financial-times/x-gift-article renders a default With native share on App x-gift-article 1`] = ` -<div - className="GiftArticle_container__2eZcF" -> - <form - className="GiftArticle_share-form__flrZ_" - name="gift-form" - > - <div - arialabelledby="gift-article-title" - role="group" - > - <div - className="GiftArticle_title__1i_Hv" - id="gift-article-title" - > - Share this article (on App) - </div> - <div - aria-labelledby="article-share-options" - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" - role="group" - > - <span - className="GiftArticle_share-option-title__1CFAK" - id="article-share-options" - > - Article share options - </span> - <label - htmlFor="giftLink" - > - <input - checked={true} - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - anyone - </strong> - (uses 1 gift credit) - </span> - </label> - <label - htmlFor="nonGiftLink" - > - <input - checked={false} - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - other FT subscribers - </strong> - </span> - </label> - </div> - <div - className="js-gift-article__url-section GiftArticle_url-section__ISfN-" - data-section-id="giftLink" - data-trackable="giftLink" - > - <span - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" - > - <input - aria-label="Gift article shareable link" - className="GiftArticle_url-input__1vM0-" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> - </span> - <div - className="GiftArticle_message__3UjVE" - > - You have - - <strong> - gift article - credits - </strong> - - left this month - </div> - <div - className="GiftArticle_buttons__1zOQK" - > - <button - className="GiftArticle_buttonBaseStyle__2ViHW" - disabled={true} - onClick={[Function]} - type="button" - > - Create gift link - </button> - </div> - </div> - </div> - </form> -</div> -`; - -exports[`@financial-times/x-gift-article renders a default Without gift credits x-gift-article 1`] = ` -<div - className="GiftArticle_container__2eZcF" -> - <form - className="GiftArticle_share-form__flrZ_" - name="gift-form" - > - <div - arialabelledby="gift-article-title" - role="group" - > - <div - className="GiftArticle_title__1i_Hv" - id="gift-article-title" - > - Share this article (without credit) - </div> - <div - aria-labelledby="article-share-options" - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--radio-round__2_6YL GiftArticle_o-forms-input--inline__1816j GiftArticle_o-forms-field__3VFUz GiftArticle_radio-button-section__qCYDb" - role="group" - > - <span - className="GiftArticle_share-option-title__1CFAK" - id="article-share-options" - > - Article share options - </span> - <label - htmlFor="giftLink" - > - <input - checked={true} - id="giftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="giftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - anyone - </strong> - (uses 1 gift credit) - </span> - </label> - <label - htmlFor="nonGiftLink" - > - <input - checked={false} - id="nonGiftLink" - name="gift-form__radio" - onChange={[Function]} - type="radio" - value="nonGiftLink" - /> - <span - className="GiftArticle_o-forms-input__label__1JFlX" - > - with - <strong> - other FT subscribers - </strong> - </span> - </label> - </div> - <div - className="js-gift-article__url-section GiftArticle_url-section__ISfN-" - data-section-id="giftLink" - data-trackable="giftLink" - > - <span - className="GiftArticle_o-forms-input__1bOBy GiftArticle_o-forms-input--text__1CetU" - > - <input - aria-label="Gift article shareable link" - className="GiftArticle_url-input__1vM0-" - disabled={true} - name="example-gift-link" - readOnly={true} - type="text" - value="https://on.ft.com/gift_link" - /> - </span> - <div - className="GiftArticle_message__3UjVE" - > - You have - - <strong> - gift article - credits - </strong> - - left this month - </div> - <div - className="GiftArticle_buttons__1zOQK" - > - <button - className="GiftArticle_buttonBaseStyle__2ViHW" - disabled={true} - onClick={[Function]} - type="button" - > - Create gift link - </button> - </div> - </div> - </div> - </form> -</div> -`; - -exports[`@financial-times/x-podcast-launchers renders a default Example x-podcast-launchers 1`] = ` -<div - className="PodcastLaunchers_container__1418-" - data-trackable="podcast-launchers" -> - <h2 - className="PodcastLaunchers_headingChooseApp__19hr-" - > - Subscribe via your installed podcast app - </h2> - <ul - className="PodcastLaunchers_podcastAppLinksWrapper__7W6dn" - > - <li> - <a - className="PodcastLaunchers_podcastAppLink__69RMR" - data-trackable="apple-podcasts" - href="podcast://access.acast.com/rss/therachmanreview/abc-123" - > - Apple Podcasts - </a> - </li> - <li> - <a - className="PodcastLaunchers_podcastAppLink__69RMR" - data-trackable="overcast" - href="overcast://x-callback-url/add?url=https://access.acast.com/rss/therachmanreview/abc-123" - > - Overcast - </a> - </li> - <li> - <a - className="PodcastLaunchers_podcastAppLink__69RMR" - data-trackable="pocket-casts" - href="pktc://subscribe/access.acast.com/rss/therachmanreview/abc-123" - > - Pocket Casts - </a> - </li> - <li> - <a - className="PodcastLaunchers_podcastAppLink__69RMR" - data-trackable="podcast-addict" - href="podcastaddict://access.acast.com/rss/therachmanreview/abc-123" - > - Podcast Addict - </a> - </li> - <li> - <a - className="PodcastLaunchers_podcastAppLink__69RMR" - data-trackable="acast" - href="acast://subscribe/https://access.acast.com/rss/therachmanreview/abc-123" - > - Acast - </a> - </li> - <li - className="PodcastLaunchers_rssUrlWrapper__vc1zt" - > - <span - className="PodcastLaunchers_o-forms-input--suffix__rDClE PodcastLaunchers_o-forms-input--text__2jeJ0 PodcastLaunchers_o-forms-input__dI9dQ" - > - <input - readOnly={true} - type="text" - value="https://access.acast.com/rss/therachmanreview/abc-123" - /> - <button - className="PodcastLaunchers_rssUrlCopyButton__2_4c0" - data-trackable="copy-rss" - data-url="https://access.acast.com/rss/therachmanreview/abc-123" - onClick={[Function]} - type="button" - > - Copy RSS - </button> - </span> - </li> - </ul> - <div - className="podcast-launchers__no-app-wrapper PodcastLaunchers_noAppWrapper__3AbOY" - > - <h2 - className="PodcastLaunchers_headingNoApp__3uLqT" - > - Can’t see your podcast app? - </h2> - <p - className="PodcastLaunchers_textNoApp__lmVJ1" - > - Get updates for new episodes - </p> - <form - action="/__myft/api/core/followed/concept/e0f2acb4-4177-436d-a783-b8c80ec2a6ac?method=put" - data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" - method="GET" - onSubmit={[Function]} - > - <input - data-myft-csrf-token={true} - name="token" - type="hidden" - value="token" - /> - <button - aria-label="Add Rachman Review to myFT" - aria-pressed="false" - className="main_button__3Mk67" - dangerouslySetInnerHTML={ - Object { - "__html": "Add to myFT", - } - } - data-concept-id="e0f2acb4-4177-436d-a783-b8c80ec2a6ac" - data-trackable="follow" - data-trackable-context-messaging={null} - title="Add Rachman Review to myFT" - type="submit" - /> - </form> - </div> -</div> -`; - -exports[`@financial-times/x-styling-demo renders a default Styling x-styling-demo 1`] = ` -<button - className="Button_button__vS7Mv" -> - Click me! -</button> -`; - -exports[`@financial-times/x-teaser-timeline renders a default Timeline x-teaser-timeline 1`] = ` -<div> - <section - className="TeaserTimeline_itemGroup__2YuVE" - > - <h2 - className="TeaserTimeline_itemGroup__heading__3KrJD" - > - Latest News - </h2> - <ul - className="TeaserTimeline_itemGroup__items__3ZuL6" - > - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="65867e26-d203-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit Briefing" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" - > - Is December looming as the new Brexit deadline? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - As Theresa May prepares to meet EU leaders, the prospect looks increasingly likely - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/65867e26-d203-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fc8a8d52e-d20a-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/65867e26-d203-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="65867e26-d203-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Is December looming as the new Brexit deadline? to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="65867e26-d203-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="93614586-d1f1-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" - > - May seeks to overcome deadlock after Brexit setback - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - UK and EU consider extending transition deal to defuse dispute over Irish border backstop - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/93614586-d1f1-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F8ba50178-d216-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/93614586-d1f1-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="93614586-d1f1-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Saved to myFT" - aria-pressed={true} - className="ArticleSaveButton_button__2_wUr" - data-content-id="93614586-d1f1-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Saved - </button> - </form> - </div> - </li> - </ul> - </section> - <section - className="TeaserTimeline_itemGroup__2YuVE" - > - <h2 - className="TeaserTimeline_itemGroup__heading__3KrJD" - > - Earlier Today - </h2> - <ul - className="TeaserTimeline_itemGroup__items__3ZuL6" - > - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - > - Midsized UK businesses turn sour on Brexit - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Quarterly survey shows more companies now believe Brexit will damage their business than help them - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa3695666-d201-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Midsized UK businesses turn sour on Brexit to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="d4e80114-d1ee-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - dangerouslySetInnerHTML={ - Object { - "__html": "Custom slot content", - } - } - /> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="6582b8ce-d175-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - > - Barnier open to extending Brexit transition by a year - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - In return, Theresa May must accept ‘two-tier’ backstop to avoid a hard Irish border - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F9be47d9e-d17a-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/6582b8ce-d175-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="6582b8ce-d175-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Barnier open to extending Brexit transition by a year to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="6582b8ce-d175-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image o-teaser--opinion js-teaser" - data-id="7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <span - className="o-teaser__tag-prefix" - > - Inside Business - </span> - <a - aria-label="Category: Sarah Gordon" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/sarah-gordon" - > - Sarah Gordon - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - > - UK lets down business with lack of Brexit advice - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Governments should be more concerned about SMEs: if supply chains falter, so will economic growth - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F23529cb0-d140-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save UK lets down business with lack of Brexit advice to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="7ab52d68-d11a-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="868cedae-d18a-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Global trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/global-trade" - > - Global trade - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" - > - Trump looks to start formal US-UK trade talks - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - White House makes London a priority ‘as soon as it is ready’ after Brexit - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/868cedae-d18a-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Ff08f8340-d1a0-11e8-a9f2-7574db66bcd5?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/868cedae-d18a-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="868cedae-d18a-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Trump looks to start formal US-UK trade talks to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="868cedae-d18a-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - </ul> - </section> - <section - className="TeaserTimeline_itemGroup__2YuVE" - > - <h2 - className="TeaserTimeline_itemGroup__heading__3KrJD" - > - Yesterday - </h2> - <ul - className="TeaserTimeline_itemGroup__items__3ZuL6" - > - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="c276c8a2-d159-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: House of Commons UK" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/fa354fe2-b2e0-483a-9267-cffe6ef9083b" - > - House of Commons UK - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - > - Bercow faces mounting calls to resign - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Maria Miller says Speaker needs to step down immediately to ‘drive culture change’ - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fece474ca-d151-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/c276c8a2-d159-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="c276c8a2-d159-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Bercow faces mounting calls to resign to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="c276c8a2-d159-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image o-teaser--opinion js-teaser" - data-id="a1566658-d12e-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <span - className="o-teaser__tag-prefix" - > - The FT View - </span> - <a - aria-label="Category: The editorial board" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/ft-view" - > - The editorial board - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" - > - A culture shift to clear Westminster’s toxic air - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Speaker John Bercow should take responsibility — and go now - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/a1566658-d12e-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F5319de7e-d12f-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/a1566658-d12e-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="a1566658-d12e-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save A culture shift to clear Westminster’s toxic air to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="a1566658-d12e-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - > - EU demands UK break Brexit impasse - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - May tells Eurosceptics acceptable deal is in sight as Barnier says ‘Brits need more time’ - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F62fd9e46-d14f-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save EU demands UK break Brexit impasse to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="f9f69a2c-d141-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit Briefing" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - > - Brexit, Scotland and the threat to constitutional unity - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Scottish Tories fear that possible arrangements for Northern Ireland threaten the union itself - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fcb11f55e-d140-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Brexit, Scotland and the threat to constitutional unity to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="eb6062c2-d13c-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="d7472b20-d148-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" - > - EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/d7472b20-d148-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F383a4ea2-d14a-11e8-a9f2-7574db66bcd5?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/d7472b20-d148-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="d7472b20-d148-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save EU’s Tusk to ask UK for ‘concrete proposals’ to end Brexit impasse to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="d7472b20-d148-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Wells Fargo" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/1120e446-6695-4e49-91e6-fd1f7698388e" - > - Wells Fargo - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - > - Wells Fargo applies for licence in France as part of Brexit strategy - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F31d2be9e-d13d-11e8-a9f2-7574db66bcd5?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Wells Fargo applies for licence in France as part of Brexit strategy to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="10ee3a20-d13b-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: UK welfare reform" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/c4c9204f-8335-42c3-9415-c2c8cd971125" - > - UK welfare reform - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - > - Rollout of controversial UK welfare reform faces fresh delay - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Universal credit system not expected to be fully operational until December 2023 - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fd7517de4-d131-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Rollout of controversial UK welfare reform faces fresh delay to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="6bd7f80e-d11d-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser js-teaser" - data-id="1969887a-d11e-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Pound Sterling" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" - > - Pound rallies after upbeat wage growth reading - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/1969887a-d11e-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Analysts remain cautious despite optimistic data - </a> - </p> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/1969887a-d11e-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="1969887a-d11e-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Pound rallies after upbeat wage growth reading to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="1969887a-d11e-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: UK politics & policy" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world/uk/politics" - > - UK politics & policy - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - > - Problem gambling shake-up set to be brought forward - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - tabIndex={-1} - > - Blow for bookmakers as ministers seek to speed up start of fixed-odds betting terminals limits - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fa6afae18-d069-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - className="ArticleSaveButton_root__Utel0" - data-content-id="dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Problem gambling shake-up set to be brought forward to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="dab8bfd2-ce3f-11e8-9fe5-24ad351828ab" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Technology sector" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/technology" - > - Technology sector - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - > - Crypto exchange Coinbase sets up Brexit contingency - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - US digital start-up to scale up EU operations with new Dublin branch - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F9a6adda6-d07a-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Crypto exchange Coinbase sets up Brexit contingency to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="d5ebbb7e-d07b-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - </ul> - </section> - <section - className="TeaserTimeline_itemGroup__2YuVE" - > - <h2 - className="TeaserTimeline_itemGroup__heading__3KrJD" - > - October 15, 2018 - </h2> - <ul - className="TeaserTimeline_itemGroup__items__3ZuL6" - > - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="2e407a74-d06a-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: World" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/world" - > - World - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - > - EU gives UK 24 hour Brexit breathing space - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Theresa May says deal achievable but Britain cannot be kept in backstop indefinitely - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fac0fd098-d075-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/2e407a74-d06a-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="2e407a74-d06a-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save EU gives UK 24 hour Brexit breathing space to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="2e407a74-d06a-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser js-teaser" - data-id="434dc8e2-d09f-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" - > - EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ - </a> - </div> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/434dc8e2-d09f-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="434dc8e2-d09f-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save EC’s Tusk warns no-deal Brexit ‘is more likely than ever before’ to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="434dc8e2-d09f-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="fadfb212-d091-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" - > - Barnier plan no solution to Irish border, says Foster - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/fadfb212-d091-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fd0cbda02-cbb7-11e8-8d0b-a6539b949662?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/fadfb212-d091-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="fadfb212-d091-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Barnier plan no solution to Irish border, says Foster to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="fadfb212-d091-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="79939f94-c320-11e8-8d55-54197280d3f7" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <span - className="o-teaser__tag-prefix" - > - Explainer - </span> - <a - aria-label="Category: Pound Sterling" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/stream/466a4700-307f-47cc-83f1-c5f97a172232" - > - Pound Sterling - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/79939f94-c320-11e8-8d55-54197280d3f7" - > - Pound timeline: from the 1970s to Brexit crunch - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/79939f94-c320-11e8-8d55-54197280d3f7" - tabIndex={-1} - > - Sterling has experienced several periods of volatility in the past 48 years - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/79939f94-c320-11e8-8d55-54197280d3f7" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Faea311c6-c32d-11e8-95b1-d36dfef1b89a?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/79939f94-c320-11e8-8d55-54197280d3f7" - className="ArticleSaveButton_root__Utel0" - data-content-id="79939f94-c320-11e8-8d55-54197280d3f7" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Pound timeline: from the 1970s to Brexit crunch to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="79939f94-c320-11e8-8d55-54197280d3f7" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="bfffa642-d079-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" - > - Brexit talks could drag into December warns Ireland’s Varadkar - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - Taoiseach says he always believed a deal this month was unlikely - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/bfffa642-d079-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Ff87f782a-d089-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/bfffa642-d079-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="bfffa642-d079-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Brexit talks could drag into December warns Ireland’s Varadkar to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="bfffa642-d079-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="82ae2756-d073-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit Briefing" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit-briefing" - > - Brexit Briefing - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" - > - Crisis or choreography over Brexit? - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" - tabIndex={-1} - > - While ministers haggle, company bosses press the button on expensive no-deal planning - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/82ae2756-d073-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F31c1c612-d079-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/82ae2756-d073-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="82ae2756-d073-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Crisis or choreography over Brexit? to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="82ae2756-d073-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="a8181102-ce1e-11e8-9fe5-24ad351828ab" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Industrials" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/companies/industrials" - > - Industrials - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" - > - UK shipyards to submit bids to build 5 warships - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" - tabIndex={-1} - > - MoD restarts competition despite industry’s concerns over budget and timing - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/a8181102-ce1e-11e8-9fe5-24ad351828ab" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Ff1191270-ce41-11e8-8d0b-a6539b949662?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/a8181102-ce1e-11e8-9fe5-24ad351828ab" - className="ArticleSaveButton_root__Utel0" - data-content-id="a8181102-ce1e-11e8-9fe5-24ad351828ab" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save UK shipyards to submit bids to build 5 warships to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="a8181102-ce1e-11e8-9fe5-24ad351828ab" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: Brexit" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/brexit" - > - Brexit - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - > - May to address UK parliament on state of Brexit talks - </a> - </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2Fc77408fa-d065-11e8-a9f2-7574db66bcd5?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save May to address UK parliament on state of Brexit talks to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="9b3b6d2e-d064-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--has-image js-teaser" - data-id="ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: UK business & economy" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-business-economy" - > - UK business & economy - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - > - Bradford hospital launches AI powered command centre - </a> - </div> - <p - className="o-teaser__standfirst" - > - <a - className="js-teaser-standfirst-link" - data-trackable="standfirst-link" - href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - tabIndex={-1} - > - Collaboration with GE Healthcare to create hub monitoring patients to make better use of resources - </a> - </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a - aria-hidden="true" - data-trackable="image-link" - href="/content/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F66d87a12-d06f-11e8-9a3c-5d5eac8f1ab4?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - className="ArticleSaveButton_root__Utel0" - data-content-id="ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Bradford hospital launches AI powered command centre to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="ed665ed8-cdfd-11e8-9fe5-24ad351828ab" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser o-teaser--opinion js-teaser" - data-id="7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <span - className="o-teaser__tag-prefix" - > - FT Alphaville - </span> - <a - aria-label="Category: Bryce Elder" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/markets/bryce-elder" - > - Bryce Elder - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="http://ftalphaville.ft.com/marketslive/2018-10-15/" - > - Markets Live: Monday, 15th October 2018 - </a> - </div> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426" - className="ArticleSaveButton_root__Utel0" - data-content-id="7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Markets Live: Monday, 15th October 2018 to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="7c6b9fbb-ad93-34c5-b19b-ea79ffc5f426" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - <li - className="TeaserTimeline_item__1s6ow" - > - <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--timeline-teaser js-teaser" - data-id="aa9b9bda-d056-11e8-a9f2-7574db66bcd5" - > - <div - className="o-teaser__content" - > - <div - className="o-teaser__meta" - > - <a - aria-label="Category: UK trade" - className="o-teaser__tag" - data-trackable="teaser-tag" - href="/uk-trade" - > - UK trade - </a> - </div> - <div - className="o-teaser__heading" - > - <a - className="js-teaser-heading-link" - data-trackable="heading-link" - href="/content/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" - > - Irish deputy PM voices ‘frustration’ at Brexit impasse - </a> - </div> - </div> - </div> - <div - className="TeaserTimeline_itemActions__1ao7c" - > - <form - action="/myft/save/aa9b9bda-d056-11e8-a9f2-7574db66bcd5" - className="ArticleSaveButton_root__Utel0" - data-content-id="aa9b9bda-d056-11e8-a9f2-7574db66bcd5" - method="GET" - onSubmit={[Function]} - > - <button - aria-label="Save Irish deputy PM voices ‘frustration’ at Brexit impasse to myFT for later" - aria-pressed={false} - className="ArticleSaveButton_button__2_wUr" - data-content-id="aa9b9bda-d056-11e8-a9f2-7574db66bcd5" - data-trackable="save-for-later" - type="submit" - > - <span - className="ArticleSaveButton_icon__-7Con" - /> - Save - </button> - </form> - </div> - </li> - </ul> - </section> -</div> -`; diff --git a/__tests__/snapshots.test.js b/__tests__/snapshots.test.js deleted file mode 100644 index 089f5f892..000000000 --- a/__tests__/snapshots.test.js +++ /dev/null @@ -1,32 +0,0 @@ -const renderer = require('react-test-renderer') -const fs = require('fs') -const path = require('path') -const glob = require('glob') -const { h } = require('../packages/x-engine') - -const { workspaces } = require('../package.json') - -const packagesGlob = workspaces.length > 1 ? `{${workspaces.join(',')}}` : workspaces[0] - -const packageDirs = glob.sync(packagesGlob) - -for (const pkg of packageDirs) { - const pkgDir = path.resolve(pkg) - const storiesDir = path.resolve(pkgDir, 'storybook') - - if (fs.existsSync(storiesDir)) { - const { package: pkg, stories, component } = require(storiesDir) - const { presets = { default: {} } } = require(pkgDir) - const name = path.basename(pkg.name) - - for (const { title, data } of stories) { - for (const [preset, options] of Object.entries(presets)) { - it(`${pkg.name} renders a ${preset} ${title} ${name}`, () => { - const props = { ...data, ...options } - const tree = renderer.create(h(component, props)).toJSON() - expect(tree).toMatchSnapshot() - }) - } - } - } -} From d6a1c4a98906e8455586e1d486079c4f5e705b97 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Fri, 20 Nov 2020 14:02:21 +0000 Subject: [PATCH 611/760] removed empty dependencies obj from package.json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 4a7a3a084..f7ddf1c6f 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,5 @@ "components/*", "packages/*", "tools/*" - ], - "dependencies": {} + ] } From dc59c0ac8bd471a21df5d665b768e14ff3d8228a Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Fri, 20 Nov 2020 14:28:02 +0000 Subject: [PATCH 612/760] addon-controls to essentials and uses storyName --- .storybook/main.js | 2 +- components/x-gift-article/storybook/index.jsx | 24 +++++-------------- components/x-teaser/storybook/index.jsx | 12 +++------- package.json | 2 +- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/.storybook/main.js b/.storybook/main.js index 1f24b1d0f..df6cdb76b 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,4 +1,4 @@ module.exports = { stories: ['../components/**/storybook/index.jsx'], - addons: ['@storybook/addon-controls'] + addons: ['@storybook/addon-essentials'] } diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index 44e00dbef..f00d91216 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -27,9 +27,7 @@ export const WithGiftCredits = (args) => { </div> ) } -WithGiftCredits.story = { - name: 'With gift credits' -} +WithGiftCredits.storyName = 'With gift credits' WithGiftCredits.args = require('./with-gift-credits').args export const WithoutGiftCredits = (args) => { @@ -47,9 +45,7 @@ export const WithoutGiftCredits = (args) => { ) } -WithoutGiftCredits.story = { - name: 'Without gift credits' -} +WithoutGiftCredits.storyName = 'Without gift credits' WithoutGiftCredits.args = require('./without-gift-credits').args export const WithGiftLink = (args) => { @@ -67,9 +63,7 @@ export const WithGiftLink = (args) => { ) } -WithGiftLink.story = { - name: 'With gift link' -} +WithGiftLink.storyName = 'With gift link' WithGiftLink.args = require('./with-gift-link').args export const FreeArticle = (args) => { @@ -87,9 +81,7 @@ export const FreeArticle = (args) => { ) } -FreeArticle.story = { - name: 'Free article' -} +FreeArticle.storyName = 'Free article' FreeArticle.args = require('./free-article').args export const NativeShare = (args) => { @@ -107,9 +99,7 @@ export const NativeShare = (args) => { ) } -NativeShare.story = { - name: 'Native share' -} +NativeShare.storyName = 'Native share' NativeShare.args = require('./native-share').args export const ErrorResponse = (args) => { @@ -127,7 +117,5 @@ export const ErrorResponse = (args) => { ) } -ErrorResponse.story = { - name: 'Error response' -} +ErrorResponse.storyName = 'Error response' ErrorResponse.args = require('./error-response').args diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index b41fc9c1a..367ca10ff 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -84,9 +84,7 @@ export const ContentPackage = (args) => { ) } -ContentPackage.story = { - name: 'ContentPackage' -} +ContentPackage.storyName = 'ContentPackage' ContentPackage.args = require('./content-package').args ContentPackage.argTypes = argTypes @@ -104,9 +102,7 @@ export const PackageItem = (args) => { ) } -PackageItem.story = { - name: 'PackageItem' -} +PackageItem.storyName = 'PackageItem' PackageItem.args = require('./package-item').args PackageItem.argTypes = argTypes @@ -141,9 +137,7 @@ export const TopStory = (args) => { ) } -TopStory.story = { - name: 'TopStory' -} +TopStory.storyName = 'TopStory' TopStory.args = require('./top-story').args TopStory.argTypes = argTypes diff --git a/package.json b/package.json index f7ddf1c6f..cff234afd 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "devDependencies": { "@babel/core": "^7.4.5", "@financial-times/athloi": "^1.0.0-beta.26", - "@storybook/addon-controls": "^6.0.28", + "@storybook/addon-essentials": "^6.0.28", "@storybook/react": "^6.0.28", "@types/jest": "26.0.0", "@typescript-eslint/parser": "^3.0.0", From f0518b0b2ebfed2c346e0add7255a0e9f39b6575 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Fri, 27 Nov 2020 11:44:21 +0000 Subject: [PATCH 613/760] use storyName for x-privacy-manager --- .../x-privacy-manager/storybook/index.jsx | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/components/x-privacy-manager/storybook/index.jsx b/components/x-privacy-manager/storybook/index.jsx index 9fdecd657..bbb137c72 100644 --- a/components/x-privacy-manager/storybook/index.jsx +++ b/components/x-privacy-manager/storybook/index.jsx @@ -31,10 +31,7 @@ export const ConsentIndeterminate = (args) => { ) } -ConsentIndeterminate.story = { - name: 'Consent: indeterminate' -} - +ConsentIndeterminate.storyName = 'Consent: indeterminate' ConsentIndeterminate.args = defaultArgs ConsentIndeterminate.argTypes = defaultArgTypes @@ -53,9 +50,7 @@ export const ConsentAccepted = (args) => { ) } -ConsentAccepted.story = { - name: 'Consent: accepted' -} +ConsentAccepted.storyName = 'Consent: accepted' ConsentAccepted.args = { ...defaultArgs, consent: true @@ -77,9 +72,7 @@ export const ConsentBlocked = (args) => { ) } -ConsentBlocked.story = { - name: 'Consent: blocked' -} +ConsentBlocked.storyName = 'Consent: blocked' ConsentBlocked.args = { ...defaultArgs, consent: false @@ -101,8 +94,6 @@ export const SaveFailed = (args) => { ) } -SaveFailed.story = { - name: 'Save failed' -} +SaveFailed.storyName = 'Save failed' SaveFailed.args = defaultArgs SaveFailed.argTypes = defaultArgTypes From 1d969bc20c5dc4c6083101c421e48914ea398f6d Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Fri, 27 Nov 2020 12:40:13 +0000 Subject: [PATCH 614/760] Additions required to run SB with a fresh install --- .storybook/webpack.config.js | 5 ++++- package.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index ab6a7aa94..e28a91fc7 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -39,7 +39,10 @@ module.exports = ({ config }) => { // HACK: Instruct Babel to check module type before injecting Core JS polyfills // https://github.com/i-like-robots/broken-webpack-bundle-test-case - const babelConfig = jsRule.use.find(({ loader }) => loader === 'babel-loader') + const fakeConfig = { + options: { presets: [] } + } + const babelConfig = jsRule.use.find(({ loader }) => loader === 'babel-loader') || fakeConfig babelConfig.options.sourceType = 'unambiguous' // Override the Babel configuration for all x- components with our own diff --git a/package.json b/package.json index cff234afd..58d1a1267 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "node-sass": "^4.12.0", "prettier": "^2.0.2", "react": "^16.8.6", + "react-dom": "17.0.1", "react-helmet": "^5.2.0", "react-test-renderer": "^16.8.6", "sass-loader": "^7.1.0", From d1a3cb09ef106b14b33b4f567594b4f2fa1030e8 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Fri, 27 Nov 2020 12:47:14 +0000 Subject: [PATCH 615/760] Make React versions match --- .storybook/webpack.config.js | 3 +-- package.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index e28a91fc7..7a3bd3f72 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -39,10 +39,9 @@ module.exports = ({ config }) => { // HACK: Instruct Babel to check module type before injecting Core JS polyfills // https://github.com/i-like-robots/broken-webpack-bundle-test-case - const fakeConfig = { + const babelConfig = jsRule.use.find(({ loader }) => loader === 'babel-loader') || { options: { presets: [] } } - const babelConfig = jsRule.use.find(({ loader }) => loader === 'babel-loader') || fakeConfig babelConfig.options.sourceType = 'unambiguous' // Override the Babel configuration for all x- components with our own diff --git a/package.json b/package.json index 58d1a1267..00a58d936 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "node-sass": "^4.12.0", "prettier": "^2.0.2", "react": "^16.8.6", - "react-dom": "17.0.1", + "react-dom": "^16.8.6", "react-helmet": "^5.2.0", "react-test-renderer": "^16.8.6", "sass-loader": "^7.1.0", From b2fa219bd9f0f6c550536647ac27e44919239455 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Fri, 27 Nov 2020 14:12:13 +0000 Subject: [PATCH 616/760] bump version for circleci dependency cache --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4fba0763d..5f08a932e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,24 +22,24 @@ references: # cache_keys_root: &cache_keys_root keys: - - cache-root-v3-{{ .Branch }}-{{ checksum "./package.json" }} + - cache-root-v4-{{ .Branch }}-{{ checksum "./package.json" }} cache_keys_docs: &cache_keys_docs keys: - - cache-docs-v3-{{ .Branch }}-{{ checksum "./web/package.json" }} + - cache-docs-v4-{{ .Branch }}-{{ checksum "./web/package.json" }} # # Cache creation # create_cache_root: &create_cache_root save_cache: - key: cache-root-v3-{{ .Branch }}-{{ checksum "./package.json" }} + key: cache-root-v4-{{ .Branch }}-{{ checksum "./package.json" }} paths: - ./node_modules/ create_cache_docs: &create_cache_docs save_cache: - key: cache-docs-v3-{{ .Branch }}-{{ checksum "./web/package.json" }} + key: cache-docs-v4-{{ .Branch }}-{{ checksum "./web/package.json" }} paths: - ./web/node_modules/ From f10b09bac17f3414644417118cd4b88c865acb03 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Fri, 27 Nov 2020 14:49:02 +0000 Subject: [PATCH 617/760] added a .gitignore to .storybook/static --- .storybook/static/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .storybook/static/.gitignore diff --git a/.storybook/static/.gitignore b/.storybook/static/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/.storybook/static/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From 8a6a9c89d67a67a2b6cc2153fa54727cd0819290 Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Thu, 12 Nov 2020 15:36:45 +0000 Subject: [PATCH 618/760] Remove Snyk command from CircleCI publish step A Snyk error is preventing me from releasing a new version of `x-dash`. We thought this error was resolved but it looks not to be the case. Andy has reported the issue to Snyk again and we are waiting on them. Because we don't know if there will be a fix soon, I am going to remove the Snyk command, publish a new release and then re-add the Snyk command. See more info here: https://financialtimes.slack.com/archives/C3TJ6KXEU/p1605111138223300 --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5f08a932e..2b97e5fc1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,7 +124,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From 02e7a66212326bf2429027f691cfbf70a8310871 Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Thu, 12 Nov 2020 15:59:24 +0000 Subject: [PATCH 619/760] Re-add Snyk command Undoing this PR: https://github.com/Financial-Times/x-dash/pull/552 --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2b97e5fc1..5f08a932e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,6 +124,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From b85f46669ac998e7cca59247fc5112eacf7305f3 Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Mon, 16 Nov 2020 13:55:52 +0000 Subject: [PATCH 620/760] Fix bug with accessing post ID I forgot to update how we access post ID in this commit: https://github.com/Financial-Times/x-dash/pull/551/commits/be96eb0e44b6b0ef70a3112716fc768b302daf36. Essentially, `next-live-blog-trigger` renamed the post ID property from `id` to `postId` which means when we receive the event, we need to access the post id as `updated.postId`. --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 2 +- .../x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index cc89bc24c..36790dad4 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -14,7 +14,7 @@ const withLiveBlogWrapperActions = withActions({ updatePost(updated) { return (props) => { - const index = props.posts.findIndex((post) => post.id === updated.id) + const index = props.posts.findIndex((post) => post.id === updated.postId) if (index >= 0) { props.posts[index] = updated } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 2f4be81f2..6b6e5bdda 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -72,7 +72,7 @@ describe('liveBlogWrapperActions', () => { it('updates a post', () => { const updatedPost2 = { - id: '2', + postId: '2', title: 'Updated title' } From bdc410a1a282085b6c53c8209457a29c29779717 Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Mon, 16 Nov 2020 14:28:00 +0000 Subject: [PATCH 621/760] Remove failing Snyk CircleCI command A Snyk error is preventing me from releasing a new version of `x-dash`. Snyk is aware of the issue and Andy is working with them to debug. Because we don't know if there will be a fix soon, I am going to remove the Snyk command, publish a new release and then re-add the Snyk command. --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5f08a932e..2b97e5fc1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,7 +124,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From ec4b45b8e5d7fb29c22e80ac8fd12cec3d35f2ac Mon Sep 17 00:00:00 2001 From: Keran Braich <30316203+ker-an@users.noreply.github.com> Date: Mon, 16 Nov 2020 15:55:01 +0000 Subject: [PATCH 622/760] Revert "Remove failing Snyk CircleCI command" --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2b97e5fc1..5f08a932e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,6 +124,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From 9329be815c4117e270278e2d3b07c382fbe35ffa Mon Sep 17 00:00:00 2001 From: andygout <andygout@hotmail.co.uk> Date: Tue, 17 Nov 2020 08:16:08 +0000 Subject: [PATCH 623/760] Remove Snyk monitor command from CircleCI config --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5f08a932e..bf49dbae9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,7 +124,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} @@ -139,7 +138,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Extract tag name and version number command: | From fc2d3ee044cb4769d49ef82fb0c507521198c9f1 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Mon, 30 Nov 2020 12:49:09 +0000 Subject: [PATCH 624/760] remove pkg usage for styles in storybook components --- components/x-gift-article/storybook/index.jsx | 49 +++++++------------ .../x-podcast-launchers/storybook/index.jsx | 9 ++-- .../x-privacy-manager/storybook/index.jsx | 33 +++++-------- components/x-styling-demo/storybook/index.jsx | 11 ++--- .../x-teaser-timeline/storybook/index.jsx | 9 ++-- components/x-teaser/storybook/index.jsx | 44 ----------------- 6 files changed, 39 insertions(+), 116 deletions(-) diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index f00d91216..83c786ae0 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -3,7 +3,6 @@ import fetchMock from 'fetch-mock' import React from 'react' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -const pkg = require('../package.json') const dependencies = { 'o-fonts': '^3.0.0' @@ -18,11 +17,9 @@ export const WithGiftCredits = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> <GiftArticle {...args} /> </div> ) @@ -35,11 +32,9 @@ export const WithoutGiftCredits = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> <GiftArticle {...args} /> </div> ) @@ -53,11 +48,9 @@ export const WithGiftLink = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> <GiftArticle {...args} /> </div> ) @@ -71,11 +64,9 @@ export const FreeArticle = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> <GiftArticle {...args} /> </div> ) @@ -89,11 +80,9 @@ export const NativeShare = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> <GiftArticle {...args} /> </div> ) @@ -107,11 +96,9 @@ export const ErrorResponse = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> <GiftArticle {...args} /> </div> ) diff --git a/components/x-podcast-launchers/storybook/index.jsx b/components/x-podcast-launchers/storybook/index.jsx index 8bcb0e54f..30d727727 100644 --- a/components/x-podcast-launchers/storybook/index.jsx +++ b/components/x-podcast-launchers/storybook/index.jsx @@ -3,7 +3,6 @@ const { brand } = require('@financial-times/n-concept-ids') import React from 'react' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -const pkg = require('../package.json') // Set up basic document styling using the Origami build service const dependencies = { @@ -21,11 +20,9 @@ export const Example = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-podcast-launchers/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-podcast-launchers/dist/PodcastLaunchers.css`} /> + </Helmet> <PodcastLaunchers {...args} /> </div> ) diff --git a/components/x-privacy-manager/storybook/index.jsx b/components/x-privacy-manager/storybook/index.jsx index bbb137c72..31f226eb6 100644 --- a/components/x-privacy-manager/storybook/index.jsx +++ b/components/x-privacy-manager/storybook/index.jsx @@ -4,7 +4,6 @@ import fetchMock from 'fetch-mock' import React from 'react' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -const pkg = require('../package.json') const dependencies = { 'o-loading': '^4.0.0', @@ -21,11 +20,9 @@ export const ConsentIndeterminate = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> + </Helmet> <PrivacyManager {...args} /> </div> ) @@ -40,11 +37,9 @@ export const ConsentAccepted = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> + </Helmet> <PrivacyManager {...args} /> </div> ) @@ -62,11 +57,9 @@ export const ConsentBlocked = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> + </Helmet> <PrivacyManager {...args} /> </div> ) @@ -84,11 +77,9 @@ export const SaveFailed = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> + </Helmet> <PrivacyManager {...args} /> </div> ) diff --git a/components/x-styling-demo/storybook/index.jsx b/components/x-styling-demo/storybook/index.jsx index bd75d3087..40758b852 100644 --- a/components/x-styling-demo/storybook/index.jsx +++ b/components/x-styling-demo/storybook/index.jsx @@ -1,9 +1,6 @@ const { Button } = require('../dist/Button.cjs') import React from 'react' import { Helmet } from 'react-helmet' -const path = require('path') -const pkg = require('../package.json') -const name = path.basename(pkg.name) export default { title: 'x-styling-demo' @@ -12,11 +9,9 @@ export default { export const Styling = (args) => { return ( <div className="story-container"> - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/@financial-times/x-styling-demo/dist/Button.css`} /> + </Helmet> <Button {...args} /> </div> ) diff --git a/components/x-teaser-timeline/storybook/index.jsx b/components/x-teaser-timeline/storybook/index.jsx index 1c2d8edf3..0f28daf11 100644 --- a/components/x-teaser-timeline/storybook/index.jsx +++ b/components/x-teaser-timeline/storybook/index.jsx @@ -2,7 +2,6 @@ const { TeaserTimeline } = require('../dist/TeaserTimeline.cjs') import React from 'react' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -const pkg = require('../package.json') const { args, argTypes } = require('./timeline') const dependencies = { @@ -19,11 +18,9 @@ export const Timeline = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-teaser-timeline/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-teaser-timeline/dist/TeaserTimeline.css`} /> + </Helmet> <TeaserTimeline {...args} /> </div> ) diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index 367ca10ff..b0552f86f 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -1,10 +1,6 @@ const { Teaser } = require('../') import React from 'react' -import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -const path = require('path') -const pkg = require('../package.json') -const name = path.basename(pkg.name) const dependencies = { 'o-date': '^4.0.0', @@ -25,11 +21,6 @@ export const Article = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} <Teaser {...args} /> </div> ) @@ -42,11 +33,6 @@ export const Podcast = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} <Teaser {...args} /> </div> ) @@ -58,11 +44,6 @@ export const Opinion = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} <Teaser {...args} /> </div> ) @@ -74,11 +55,6 @@ export const ContentPackage = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} <Teaser {...args} /> </div> ) @@ -92,11 +68,6 @@ export const PackageItem = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} <Teaser {...args} /> </div> ) @@ -110,11 +81,6 @@ export const Promoted = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} <Teaser {...args} /> </div> ) @@ -127,11 +93,6 @@ export const TopStory = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} <Teaser {...args} /> </div> ) @@ -145,11 +106,6 @@ export const Video = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} <Teaser {...args} /> </div> ) From f510782eb810c9a8d68e804069e7b0634c2b40ec Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Mon, 30 Nov 2020 17:04:25 +0000 Subject: [PATCH 625/760] Fix URL error When attempting to view `x-teaser` in Storybook, I came across a `TypeError: URL is not a constructor` on the page and I see the same error when running `next-front-page` locally. When I remove this line of code, the error disappears and I'm able to view the `x-teaser` stories. This line of code was added 2 months ago to restore compatibility with Node v6, see https://github.com/Financial-Times/x-dash/commit/24984503bb33142d544eaeb0fba4751310c21d4b. Not sure if removing this line of code is the best fix so would like your input. --- components/x-teaser/src/concerns/image-service.js | 1 - 1 file changed, 1 deletion(-) diff --git a/components/x-teaser/src/concerns/image-service.js b/components/x-teaser/src/concerns/image-service.js index 5981e3d2c..e77037dfd 100644 --- a/components/x-teaser/src/concerns/image-service.js +++ b/components/x-teaser/src/concerns/image-service.js @@ -1,4 +1,3 @@ -const { URL, URLSearchParams } = require('url') const BASE_URL = 'https://www.ft.com/__origami/service/image/v2/images/raw' const OPTIONS = { source: 'next', fit: 'scale-down', dpr: 2 } From 3d5d19f2a66dfb44cb4c4c427369455cca9a2772 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Tue, 1 Dec 2020 08:33:54 +0000 Subject: [PATCH 626/760] x-privacy-manager add default referrer --- components/x-privacy-manager/storybook/data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-privacy-manager/storybook/data.js b/components/x-privacy-manager/storybook/data.js index 6cd708b5d..f321aad63 100644 --- a/components/x-privacy-manager/storybook/data.js +++ b/components/x-privacy-manager/storybook/data.js @@ -16,7 +16,7 @@ const referrers = { 'pwmnet.com': 'www.pwmnet.com', 'thebanker.com': 'www.thebanker.com', 'thebankerdatabase.com': 'www.thebankerdatabase.com', - Undefined: '' + Default: '' } const legislation = { From d7bce6ff6eceeaf8e4f4f4822889d725dbeee478 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Tue, 1 Dec 2020 08:48:56 +0000 Subject: [PATCH 627/760] storybook x-teaser argument fixes --- components/x-teaser/__fixtures__/content-package.json | 2 +- components/x-teaser/__fixtures__/opinion.json | 1 + components/x-teaser/__fixtures__/package-item.json | 4 ++-- components/x-teaser/storybook/content-package.js | 4 +--- components/x-teaser/storybook/package-item.js | 5 +---- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/components/x-teaser/__fixtures__/content-package.json b/components/x-teaser/__fixtures__/content-package.json index d3c842a1f..33b749cd3 100644 --- a/components/x-teaser/__fixtures__/content-package.json +++ b/components/x-teaser/__fixtures__/content-package.json @@ -28,5 +28,5 @@ "accessLevel": "free", "theme": "", "parentTheme": "", - "modifiers": "" + "modifiers": "centre" } diff --git a/components/x-teaser/__fixtures__/opinion.json b/components/x-teaser/__fixtures__/opinion.json index 541dd1f10..482fa0394 100644 --- a/components/x-teaser/__fixtures__/opinion.json +++ b/components/x-teaser/__fixtures__/opinion.json @@ -28,6 +28,7 @@ "isOpinion": true, "isColumn": true }, + "showHeadshot": true, "status": "", "headshotTint": "", "accessLevel": "free", diff --git a/components/x-teaser/__fixtures__/package-item.json b/components/x-teaser/__fixtures__/package-item.json index 59185d427..e04bcf4f3 100644 --- a/components/x-teaser/__fixtures__/package-item.json +++ b/components/x-teaser/__fixtures__/package-item.json @@ -24,6 +24,6 @@ "headshotTint": "", "accessLevel": "free", "theme": "", - "parentTheme": "", - "modifiers": "" + "parentTheme": "extra-article", + "modifiers": "centre" } diff --git a/components/x-teaser/storybook/content-package.js b/components/x-teaser/storybook/content-package.js index d57248a09..cbbdd5e57 100644 --- a/components/x-teaser/storybook/content-package.js +++ b/components/x-teaser/storybook/content-package.js @@ -1,8 +1,6 @@ const { presets } = require('../') -exports.args = Object.assign(require('../__fixtures__/content-package.json'), presets.Hero, { - modifiers: 'centre' -}) +exports.args = Object.assign(require('../__fixtures__/content-package.json'), presets.Hero) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> diff --git a/components/x-teaser/storybook/package-item.js b/components/x-teaser/storybook/package-item.js index 1bb78775a..dab66e3a9 100644 --- a/components/x-teaser/storybook/package-item.js +++ b/components/x-teaser/storybook/package-item.js @@ -1,9 +1,6 @@ const { presets } = require('../') -exports.args = Object.assign(require('../__fixtures__/package-item.json'), presets.Hero, { - parentTheme: 'extra-article', - modifiers: 'centre' -}) +exports.args = Object.assign(require('../__fixtures__/package-item.json'), presets.Hero) // This reference is only required for hot module loading in development // <https://webpack.js.org/concepts/hot-module-replacement/> From 735a725ffe62fedf81116d124de15c7d4fe6a7c0 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Tue, 1 Dec 2020 09:13:04 +0000 Subject: [PATCH 628/760] x-teaser updated snapshots --- .../__snapshots__/snapshots.test.js.snap | 233 +++++++----------- 1 file changed, 90 insertions(+), 143 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 766cb736b..2f3f73903 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -122,7 +122,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with article-with-data-image exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] = ` <div - className="o-teaser o-teaser--package o-teaser--hero o-teaser--has-image js-teaser" + className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" data-id="" > <div @@ -182,7 +182,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -211,38 +211,21 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` Anti-Semitism and the threat of identity politics </a> </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a + <img + alt="" aria-hidden="true" - data-trackable="image-link" - href="#" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&dpr=2&width=340" - /> - </div> - </a> + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; exports[`x-teaser / snapshots renders a Hero teaser with packageItem data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-image o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" data-id="" > <div @@ -657,7 +640,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with article-with-data exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage data 1`] = ` <div - className="o-teaser o-teaser--package o-teaser--hero js-teaser" + className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre js-teaser" data-id="" > <div @@ -704,7 +687,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage da exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -745,13 +728,21 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] Today, hatred of Jews is mixed in with fights about Islam and Israel </a> </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; exports[`x-teaser / snapshots renders a HeroNarrow teaser with packageItem data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--opinion js-teaser" data-id="" > <div @@ -1187,7 +1178,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage d exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-image o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -1216,31 +1207,14 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] Anti-Semitism and the threat of identity politics </a> </div> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a + <img + alt="" aria-hidden="true" - data-trackable="image-link" - href="#" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&dpr=2&width=640" - /> - </div> - </a> + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; @@ -1638,7 +1612,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with article-with-data- exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage data 1`] = ` <div - className="o-teaser o-teaser--package o-teaser--hero js-teaser" + className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre js-teaser" data-id="" > <div @@ -1673,7 +1647,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage dat exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--hero o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -1702,13 +1676,21 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = Anti-Semitism and the threat of identity politics </a> </div> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; exports[`x-teaser / snapshots renders a HeroVideo teaser with packageItem data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--hero o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--hero o-teaser--centre o-teaser--opinion js-teaser" data-id="" > <div @@ -2104,7 +2086,7 @@ exports[`x-teaser / snapshots renders a Large teaser with article-with-data-imag exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1`] = ` <div - className="o-teaser o-teaser--package o-teaser--large o-teaser--has-image js-teaser" + className="o-teaser o-teaser--package o-teaser--large o-teaser--centre o-teaser--has-image js-teaser" data-id="" > <div @@ -2176,7 +2158,7 @@ exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1` exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--large o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -2217,38 +2199,21 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` Today, hatred of Jews is mixed in with fights about Islam and Israel </a> </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a + <img + alt="" aria-hidden="true" - data-trackable="image-link" - href="#" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&dpr=2&width=340" - /> - </div> - </a> + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; exports[`x-teaser / snapshots renders a Large teaser with packageItem data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--large o-teaser--has-image o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--large o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" data-id="" > <div @@ -2699,7 +2664,7 @@ exports[`x-teaser / snapshots renders a Small teaser with article-with-data-imag exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1`] = ` <div - className="o-teaser o-teaser--package o-teaser--small js-teaser" + className="o-teaser o-teaser--package o-teaser--small o-teaser--centre js-teaser" data-id="" > <div @@ -2734,7 +2699,7 @@ exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1` exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -2763,13 +2728,21 @@ exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` Anti-Semitism and the threat of identity politics </a> </div> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; exports[`x-teaser / snapshots renders a Small teaser with packageItem data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--small o-teaser--centre o-teaser--opinion js-teaser" data-id="" > <div @@ -3109,7 +3082,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article-with-data exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage data 1`] = ` <div - className="o-teaser o-teaser--package o-teaser--small o-teaser--has-image js-teaser" + className="o-teaser o-teaser--package o-teaser--small o-teaser--centre o-teaser--has-image js-teaser" data-id="" > <div @@ -3181,7 +3154,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage da exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--small o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -3222,38 +3195,21 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] Today, hatred of Jews is mixed in with fights about Islam and Israel </a> </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a + <img + alt="" aria-hidden="true" - data-trackable="image-link" - href="#" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&dpr=2&width=240" - /> - </div> - </a> + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; exports[`x-teaser / snapshots renders a SmallHeavy teaser with packageItem data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--small o-teaser--has-image o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--small o-teaser--centre o-teaser--has-image o-teaser--opinion js-teaser" data-id="" > <div @@ -3728,7 +3684,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with article-with-data-i exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data 1`] = ` <div - className="o-teaser o-teaser--package o-teaser--top-story js-teaser" + className="o-teaser o-teaser--package o-teaser--top-story o-teaser--centre js-teaser" data-id="" > <div @@ -3775,7 +3731,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -3816,13 +3772,21 @@ exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = Today, hatred of Jews is mixed in with fights about Islam and Israel </a> </p> + <img + alt="" + aria-hidden="true" + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; exports[`x-teaser / snapshots renders a TopStory teaser with packageItem data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--centre o-teaser--opinion js-teaser" data-id="" > <div @@ -4331,7 +4295,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPac exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion data 1`] = ` <div - className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-image o-teaser--opinion js-teaser" + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--has-headshot o-teaser--opinion js-teaser" data-id="" > <div @@ -4372,31 +4336,14 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da Today, hatred of Jews is mixed in with fights about Islam and Israel </a> </p> - </div> - <div - className="o-teaser__image-container js-teaser-image-container" - > - <a + <img + alt="" aria-hidden="true" - data-trackable="image-link" - href="#" - tabIndex="-1" - > - <div - className="o-teaser__image-placeholder" - style={ - Object { - "paddingBottom": "56.2500%", - } - } - > - <img - alt="" - className="o-teaser__image" - src="https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fprod-upp-image-read.ft.com%2F1005ca96-364b-11e8-8b98-2f31af407cc8?source=next&fit=scale-down&dpr=2&width=640" - /> - </div> - </a> + className="o-teaser__headshot" + height={75} + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + width={75} + /> </div> </div> `; From 01001f17076e3e1dad6aa5388ec632842d26d063 Mon Sep 17 00:00:00 2001 From: Glynn Phillips <glynndominicphillips@gmail.com> Date: Tue, 1 Dec 2020 20:56:40 +0000 Subject: [PATCH 629/760] Give the x-live-blog-wrapper component a displayName Without this the component wouldn't work with x-interaction when the code is transpiled for production. It was working with development code I believe because it was relying in on teh standard Javascript name property of the function. When the code was transpiled for production the functions were minified the function names were changed or made anonymous. By hardcoding this displayName as a string it allows x-interaction to register the component and reference it by name. --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 4 ++++ .../src/__tests__/LiveBlogWrapper.test.jsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 36790dad4..74bf8ca35 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -68,6 +68,10 @@ const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id, liv ) } +// A displayName is required for this component +// This enables the component to work with x-interaction +BaseLiveBlogWrapper.displayName = 'BaseLiveBlogWrapper' + const LiveBlogWrapper = withLiveBlogWrapperActions(BaseLiveBlogWrapper) export { LiveBlogWrapper, listenToLiveBlogEvents } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 6b6e5bdda..826e4cfa3 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -24,6 +24,10 @@ const post2 = { } describe('x-live-blog-wrapper', () => { + it('has a displayName', () => { + expect(LiveBlogWrapper.displayName).toContain('BaseLiveBlogWrapper') + }) + it('renders initial posts', () => { const posts = [post1, post2] const liveBlogWrapper = mount(<LiveBlogWrapper posts={posts} />) From 5ee02062ad9ad996f0616a909a2ef01f5f531871 Mon Sep 17 00:00:00 2001 From: Glynn Phillips <glynndominicphillips@gmail.com> Date: Tue, 1 Dec 2020 20:56:40 +0000 Subject: [PATCH 630/760] Give the x-live-blog-wrapper component a displayName Without this the component wouldn't work with x-interaction when the code is transpiled for production. It was working with development code I believe because it was relying in on teh standard Javascript name property of the function. When the code was transpiled for production the functions were minified the function names were changed or made anonymous. By hardcoding this displayName as a string it allows x-interaction to register the component and reference it by name. --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 4 ++++ .../src/__tests__/LiveBlogWrapper.test.jsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 36790dad4..74bf8ca35 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -68,6 +68,10 @@ const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id, liv ) } +// A displayName is required for this component +// This enables the component to work with x-interaction +BaseLiveBlogWrapper.displayName = 'BaseLiveBlogWrapper' + const LiveBlogWrapper = withLiveBlogWrapperActions(BaseLiveBlogWrapper) export { LiveBlogWrapper, listenToLiveBlogEvents } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 6b6e5bdda..826e4cfa3 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -24,6 +24,10 @@ const post2 = { } describe('x-live-blog-wrapper', () => { + it('has a displayName', () => { + expect(LiveBlogWrapper.displayName).toContain('BaseLiveBlogWrapper') + }) + it('renders initial posts', () => { const posts = [post1, post2] const liveBlogWrapper = mount(<LiveBlogWrapper posts={posts} />) From 2fba9026e745a6438eda097ad34fd60b1e292f61 Mon Sep 17 00:00:00 2001 From: Keran Braich <30316203+ker-an@users.noreply.github.com> Date: Mon, 16 Nov 2020 15:55:01 +0000 Subject: [PATCH 631/760] Revert "Remove failing Snyk CircleCI command" --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index bf49dbae9..765d3618d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,6 +124,7 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token + - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From 6c510d42728d194569ad5c0a795bcaeaf84b39c5 Mon Sep 17 00:00:00 2001 From: andygout <andygout@hotmail.co.uk> Date: Tue, 17 Nov 2020 08:16:08 +0000 Subject: [PATCH 632/760] Remove Snyk monitor command from CircleCI config --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 765d3618d..bf49dbae9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,7 +124,6 @@ jobs: - run: name: shared-helper / npm-store-auth-token command: .circleci/shared-helpers/helper-npm-store-auth-token - - run: npx snyk monitor --org=customer-products --project-name=Financial-Times/x-dash --prune-repeated-subdependencies - run: name: Bump version command: npx athloi version ${CIRCLE_TAG} From 83c295e1dee10cbeeda3b3d9b239e3f948b2425a Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Mon, 16 Nov 2020 16:18:33 +0000 Subject: [PATCH 633/760] converted buildStory to storiesOf storybook --- .storybook/storybook.utils.js | 37 +++ components/x-gift-article/storybook/index.jsx | 195 ++++++++------- .../x-live-blog-post/storybook/index.jsx | 14 +- .../x-podcast-launchers/storybook/example.js | 16 ++ .../x-podcast-launchers/storybook/index.jsx | 46 ++-- .../x-podcast-launchers/storybook/knobs.js | 5 + .../x-privacy-manager/storybook/index.jsx | 145 +++++------ components/x-styling-demo/storybook/index.jsx | 37 +++ .../x-styling-demo/storybook/styling.js | 12 + .../x-teaser-timeline/storybook/index.jsx | 40 +-- .../x-teaser-timeline/storybook/knobs.js | 19 ++ components/x-teaser/storybook/index.jsx | 232 ++++++++++-------- 12 files changed, 466 insertions(+), 332 deletions(-) create mode 100644 .storybook/storybook.utils.js create mode 100644 components/x-podcast-launchers/storybook/example.js create mode 100644 components/x-podcast-launchers/storybook/knobs.js create mode 100644 components/x-styling-demo/storybook/styling.js create mode 100644 components/x-teaser-timeline/storybook/knobs.js diff --git a/.storybook/storybook.utils.js b/.storybook/storybook.utils.js new file mode 100644 index 000000000..855e6865e --- /dev/null +++ b/.storybook/storybook.utils.js @@ -0,0 +1,37 @@ +import * as knobsAddon from '@storybook/addon-knobs' + +const defaultKnobs = () => ({}) + +/** + * Create Props + * @param {{ [key: string]: any }} defaultData + * @param {String[]} allowedKnobs + * @param {Function} hydrateKnobs + */ +function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs) { + // Inject knobs add-on into given dependency container + const knobs = hydrateKnobs(defaultData, knobsAddon) + // Mix the available knob props into default data + const mixedProps = { ...defaultData, ...knobs } + + if (allowedKnobs.length === 0) { + return mixedProps + } + + return allowedKnobs.reduce((map, prop) => { + if (mixedProps.hasOwnProperty(prop)) { + const value = mixedProps[prop] + + // Knobs are functions which need calling to register them + if (typeof value === 'function') { + map[prop] = value() + } else { + map[prop] = value + } + } + + return map + }, {}) +} + +export default createProps diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index 83c786ae0..a11ed2249 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -1,108 +1,105 @@ const { GiftArticle } = require('../dist/GiftArticle.cjs') -import fetchMock from 'fetch-mock' import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const pkg = require('../package.json') const dependencies = { 'o-fonts': '^3.0.0' } -export default { - title: 'x-gift-article' -} - -export const WithGiftCredits = (args) => { - require('./with-gift-credits').fetchMock(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> - </Helmet> - <GiftArticle {...args} /> - </div> - ) -} -WithGiftCredits.storyName = 'With gift credits' -WithGiftCredits.args = require('./with-gift-credits').args - -export const WithoutGiftCredits = (args) => { - require('./without-gift-credits').fetchMock(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> - </Helmet> - <GiftArticle {...args} /> - </div> - ) -} - -WithoutGiftCredits.storyName = 'Without gift credits' -WithoutGiftCredits.args = require('./without-gift-credits').args - -export const WithGiftLink = (args) => { - require('./with-gift-link').fetchMock(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> - </Helmet> - <GiftArticle {...args} /> - </div> - ) -} - -WithGiftLink.storyName = 'With gift link' -WithGiftLink.args = require('./with-gift-link').args - -export const FreeArticle = (args) => { - require('./free-article').fetchMock(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> - </Helmet> - <GiftArticle {...args} /> - </div> - ) -} - -FreeArticle.storyName = 'Free article' -FreeArticle.args = require('./free-article').args - -export const NativeShare = (args) => { - require('./native-share').fetchMock(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> - </Helmet> - <GiftArticle {...args} /> - </div> - ) -} - -NativeShare.storyName = 'Native share' -NativeShare.args = require('./native-share').args - -export const ErrorResponse = (args) => { - require('./error-response').fetchMock(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> - </Helmet> - <GiftArticle {...args} /> - </div> - ) -} - -ErrorResponse.storyName = 'Error response' -ErrorResponse.args = require('./error-response').args +storiesOf('x-gift-article', module) + .addDecorator(withKnobs) + .add('With gift credits', () => { + const { data, knobs: storyKnobs } = require('./with-gift-credits') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('Without gift credits', () => { + const { data, knobs: storyKnobs } = require('./without-gift-credits') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('With gift link', () => { + const { data, knobs: storyKnobs } = require('./with-gift-link') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('Free article', () => { + const { data, knobs: storyKnobs } = require('./free-article') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('Native share', () => { + const { data, knobs: storyKnobs } = require('./native-share') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) + .add('Error response', () => { + const { data, knobs: storyKnobs } = require('./error-response') + const props = createProps(data, storyKnobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> + </Helmet> + )} + <GiftArticle {...props} /> + </div> + ) + }) diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index 2108130ee..2154f6e0e 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -1,18 +1,8 @@ import React from 'react' import { LiveBlogPost } from '../src/LiveBlogPost' -export default { - title: 'x-live-blog-post', - parameters: { - escapeHTML: false - } -} - -export const ContentBody = (args) => { - return <LiveBlogPost {...args} /> -} - -ContentBody.args = { +const defaultProps = { + id: '12345', title: 'Turkey’s virus deaths may be 25% higher than official figure', isBreakingNews: false, bodyHTML: diff --git a/components/x-podcast-launchers/storybook/example.js b/components/x-podcast-launchers/storybook/example.js new file mode 100644 index 000000000..6b4e91494 --- /dev/null +++ b/components/x-podcast-launchers/storybook/example.js @@ -0,0 +1,16 @@ +const { brand } = require('@financial-times/n-concept-ids') + +exports.title = 'Example' + +exports.data = { + conceptId: brand.rachmanReviewPodcast, + conceptName: 'Rachman Review', + isFollowed: false, + csrfToken: 'token', + acastRSSHost: 'https://access.acast.com', + acastAccessToken: 'abc-123' +} + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module diff --git a/components/x-podcast-launchers/storybook/index.jsx b/components/x-podcast-launchers/storybook/index.jsx index 30d727727..adf5d2f85 100644 --- a/components/x-podcast-launchers/storybook/index.jsx +++ b/components/x-podcast-launchers/storybook/index.jsx @@ -1,8 +1,11 @@ const { PodcastLaunchers } = require('../dist/PodcastLaunchers.cjs') -const { brand } = require('@financial-times/n-concept-ids') import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const pkg = require('../package.json') // Set up basic document styling using the Origami build service const dependencies = { @@ -12,27 +15,22 @@ const dependencies = { 'o-forms': '^7.0.0' } -export default { - title: 'x-podcast-launchers' -} - -export const Example = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-podcast-launchers/dist/PodcastLaunchers.css`} /> - </Helmet> - <PodcastLaunchers {...args} /> - </div> - ) -} +const knobs = require('./knobs') -Example.args = { - conceptId: brand.rachmanReviewPodcast, - conceptName: 'Rachman Review', - isFollowed: false, - csrfToken: 'token', - acastRSSHost: 'https://access.acast.com', - acastAccessToken: 'abc-123' -} +storiesOf('x-podcast-launchers', module) + .addDecorator(withKnobs) + .add('Example', () => { + const { data, knobs: storyKnobs } = require('./example') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-podcast-launchers/${pkg.style}`} /> + </Helmet> + )} + <PodcastLaunchers {...props} /> + </div> + ) + }) diff --git a/components/x-podcast-launchers/storybook/knobs.js b/components/x-podcast-launchers/storybook/knobs.js new file mode 100644 index 000000000..4f9ada23e --- /dev/null +++ b/components/x-podcast-launchers/storybook/knobs.js @@ -0,0 +1,5 @@ +module.exports = (data, { text }) => ({ + conceptId: text('Concept id', data.conceptId), + acastRSSHost: text('Acast RSS host', data.acastRSSHost), + acastAccessToken: text('Acast Access token', data.acastAccessToken) +}) diff --git a/components/x-privacy-manager/storybook/index.jsx b/components/x-privacy-manager/storybook/index.jsx index 31f226eb6..bdb4c0f3b 100644 --- a/components/x-privacy-manager/storybook/index.jsx +++ b/components/x-privacy-manager/storybook/index.jsx @@ -1,9 +1,11 @@ const { PrivacyManager } = require('../src/privacy-manager') -const { defaultArgs, defaultArgTypes, fetchMock: privacyFM } = require('./data') -import fetchMock from 'fetch-mock' import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const pkg = require('../package.json') const dependencies = { 'o-loading': '^4.0.0', @@ -11,80 +13,67 @@ const dependencies = { 'o-typography': '^6.0.0' } -export default { - title: 'x-privacy-manager' -} - -export const ConsentIndeterminate = (args) => { - privacyFM(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> - </Helmet> - <PrivacyManager {...args} /> - </div> - ) -} - -ConsentIndeterminate.storyName = 'Consent: indeterminate' -ConsentIndeterminate.args = defaultArgs -ConsentIndeterminate.argTypes = defaultArgTypes - -export const ConsentAccepted = (args) => { - privacyFM(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> - </Helmet> - <PrivacyManager {...args} /> - </div> - ) -} - -ConsentAccepted.storyName = 'Consent: accepted' -ConsentAccepted.args = { - ...defaultArgs, - consent: true -} -ConsentAccepted.argTypes = defaultArgTypes - -export const ConsentBlocked = (args) => { - privacyFM(fetchMock) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> - </Helmet> - <PrivacyManager {...args} /> - </div> - ) -} - -ConsentBlocked.storyName = 'Consent: blocked' -ConsentBlocked.args = { - ...defaultArgs, - consent: false -} -ConsentBlocked.argTypes = defaultArgTypes - -export const SaveFailed = (args) => { - privacyFM(fetchMock, 500) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> - </Helmet> - <PrivacyManager {...args} /> - </div> - ) -} +const knobs = require('./knobs') -SaveFailed.storyName = 'Save failed' -SaveFailed.args = defaultArgs -SaveFailed.argTypes = defaultArgTypes +storiesOf('x-privacy-manager', module) + .addDecorator(withKnobs) + .add('Consent: indeterminate', () => { + const { data, knobs: storyKnobs } = require('./story-consent-indeterminate') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) + }) + .add('Consent: accepted', () => { + const { data, knobs: storyKnobs } = require('./story-consent-accepted') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) + }) + .add('Consent: blocked', () => { + const { data, knobs: storyKnobs } = require('./story-consent-blocked') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) + }) + .add('Save failed', () => { + const { data, knobs: storyKnobs } = require('./story-save-failed') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <PrivacyManager {...props} /> + </div> + ) + }) diff --git a/components/x-styling-demo/storybook/index.jsx b/components/x-styling-demo/storybook/index.jsx index 40758b852..bcb7125c8 100644 --- a/components/x-styling-demo/storybook/index.jsx +++ b/components/x-styling-demo/storybook/index.jsx @@ -1,5 +1,6 @@ const { Button } = require('../dist/Button.cjs') import React from 'react' +<<<<<<< HEAD import { Helmet } from 'react-helmet' export default { @@ -17,3 +18,39 @@ export const Styling = (args) => { ) } Styling.args = { danger: false, large: false } +======= +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' +import createProps from '../../../.storybook/storybook.utils' +const path = require('path') +const pkg = require('../package.json') +const name = path.basename(pkg.name) + +const knobs = (data, { boolean }) => ({ + danger() { + return boolean('Danger', data.danger) + }, + + large() { + return boolean('Large', data.large) + } +}) + +storiesOf('x-styling-demo', module) + .addDecorator(withKnobs) + .add('Styling', () => { + const { data, knobs: storyKnobs } = require('./styling') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Button {...props} /> + </div> + ) + }) +>>>>>>> dbbaa6f0 (converted buildStory to storiesOf storybook) diff --git a/components/x-styling-demo/storybook/styling.js b/components/x-styling-demo/storybook/styling.js new file mode 100644 index 000000000..3a0f07633 --- /dev/null +++ b/components/x-styling-demo/storybook/styling.js @@ -0,0 +1,12 @@ +exports.title = 'Styling' + +exports.data = { + danger: false, + large: false +} + +exports.knobs = ['danger', 'large'] + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module diff --git a/components/x-teaser-timeline/storybook/index.jsx b/components/x-teaser-timeline/storybook/index.jsx index 0f28daf11..e29c23561 100644 --- a/components/x-teaser-timeline/storybook/index.jsx +++ b/components/x-teaser-timeline/storybook/index.jsx @@ -1,8 +1,11 @@ const { TeaserTimeline } = require('../dist/TeaserTimeline.cjs') import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -const { args, argTypes } = require('./timeline') +import createProps from '../../../.storybook/storybook.utils' +const pkg = require('../package.json') const dependencies = { 'o-normalise': '^1.6.0', @@ -10,21 +13,22 @@ const dependencies = { 'o-teaser': '^2.3.1' } -export default { - title: 'x-teaser-timeline' -} - -export const Timeline = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Helmet> - <link rel="stylesheet" href={`components/x-teaser-timeline/dist/TeaserTimeline.css`} /> - </Helmet> - <TeaserTimeline {...args} /> - </div> - ) -} +const knobs = require('./knobs') -Timeline.args = args -Timeline.argTypes = argTypes +storiesOf('x-teaser-timeline', module) + .addDecorator(withKnobs) + .add('Timeline', () => { + const { data, knobs: storyKnobs } = require('./timeline') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-teaser-timeline/${pkg.style}`} /> + </Helmet> + )} + <TeaserTimeline {...props} /> + </div> + ) + }) diff --git a/components/x-teaser-timeline/storybook/knobs.js b/components/x-teaser-timeline/storybook/knobs.js new file mode 100644 index 000000000..56cdc5055 --- /dev/null +++ b/components/x-teaser-timeline/storybook/knobs.js @@ -0,0 +1,19 @@ +module.exports = (data, { number, select }) => { + return { + latestItemsTime() { + return select('Latest Items Time', { + None: '', + '2018-10-17T12:10:33.000Z': '2018-10-17T12:10:33.000Z' + }) + }, + customSlotPosition() { + return number('Custom Slot Position', data.customSlotPosition) + }, + customSlotContent() { + return select('Custom Slot Content', { + None: '', + Something: '---Custom slot content---' + }) + } + } +} diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index b0552f86f..209bf6d66 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -1,6 +1,13 @@ const { Teaser } = require('../') import React from 'react' +import { storiesOf } from '@storybook/react' +import { withKnobs } from '@storybook/addon-knobs' +import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' +import createProps from '../../../.storybook/storybook.utils' +const path = require('path') +const pkg = require('../package.json') +const name = path.basename(pkg.name) const dependencies = { 'o-date': '^4.0.0', @@ -11,104 +18,127 @@ const dependencies = { 'o-video': '^6.0.0' } -const { argTypes } = require('./argTypes') - -export default { - title: 'x-teaser' -} - -export const Article = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Teaser {...args} /> - </div> - ) -} - -Article.args = require('./article').args -Article.argTypes = argTypes - -export const Podcast = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Teaser {...args} /> - </div> - ) -} -Podcast.args = require('./podcast').args -Podcast.argTypes = argTypes - -export const Opinion = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Teaser {...args} /> - </div> - ) -} -Opinion.args = require('./opinion').args -Opinion.argTypes = argTypes - -export const ContentPackage = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Teaser {...args} /> - </div> - ) -} - -ContentPackage.storyName = 'ContentPackage' -ContentPackage.args = require('./content-package').args -ContentPackage.argTypes = argTypes - -export const PackageItem = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Teaser {...args} /> - </div> - ) -} - -PackageItem.storyName = 'PackageItem' -PackageItem.args = require('./package-item').args -PackageItem.argTypes = argTypes - -export const Promoted = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Teaser {...args} /> - </div> - ) -} - -Promoted.args = require('./promoted').args -Promoted.argTypes = argTypes - -export const TopStory = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Teaser {...args} /> - </div> - ) -} - -TopStory.storyName = 'TopStory' -TopStory.args = require('./top-story').args -TopStory.argTypes = argTypes - -export const Video = (args) => { - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - <Teaser {...args} /> - </div> - ) -} -Video.args = require('./video').args -Video.argTypes = argTypes +const knobs = require('./knobs') + +storiesOf('x-teaser', module) + .addDecorator(withKnobs) + .add('Article', () => { + const { data, knobs: storyKnobs } = require('./article') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('Podcast', () => { + const { data, knobs: storyKnobs } = require('./podcast') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('Opinion', () => { + const { data, knobs: storyKnobs } = require('./opinion') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('ContentPackage', () => { + const { data, knobs: storyKnobs } = require('./content-package') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('PackageItem', () => { + const { data, knobs: storyKnobs } = require('./package-item') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('Promoted', () => { + const { data, knobs: storyKnobs } = require('./promoted') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('TopStory', () => { + const { data, knobs: storyKnobs } = require('./top-story') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) + .add('Video', () => { + const { data, knobs: storyKnobs } = require('./video') + const props = createProps(data, storyKnobs, knobs) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> + </Helmet> + )} + <Teaser {...props} /> + </div> + ) + }) From d5f879e5650aedaf1b3f508f3ee084e326f1af88 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Mon, 19 Oct 2020 16:12:24 +0100 Subject: [PATCH 634/760] Make Privacy Manager legislation agnostic Up until now the component has been wholly focused on serving CCPA's needs. This was appropriate in the run-up to the deadline, but user-focused improvements make it useful for GDPR users as well. Necessary changes: - Add support for indeterminate state and add visual feedback for hover interactions - Make button text configurable - Make FoW configurable by legislation - Make tracking events configurable by legislation - Remove CCPA-specific copy: consuming apps should provide descriptive context - Update tests - Update Storybook *Additional updates* Component Architecture - Extract actions and view components into self-contained modules - Separate components from utilities - Simplify <Form /> - Consolidate messages - Break tests into smaller units TypeScript - Switch from Type aliases to interfaces where possible - Export type definitions - Declare Sass files as a module type TS can understand - Prevent ESLint from complaining about type definition files --- .eslintignore | 1 + components/x-privacy-manager/jsconfig.json | 4 + components/x-privacy-manager/package.json | 1 + .../src/__tests__/config.test.jsx | 37 +++ .../src/__tests__/helpers.js | 61 +++++ .../src/__tests__/messaging.test.jsx | 74 ++++++ .../src/__tests__/privacy-manager.test.jsx | 209 --------------- .../src/__tests__/state.test.jsx | 88 +++++++ .../src/__tests__/utils.test.js | 48 ++++ components/x-privacy-manager/src/actions.js | 83 ++++++ .../x-privacy-manager/src/components/form.jsx | 33 +++ .../src/{ => components}/messages.jsx | 28 +- .../src/components/radio-btn.jsx | 47 ++++ .../src/{ => components}/radio-btn.scss | 17 +- .../x-privacy-manager/src/privacy-manager.jsx | 249 +++++------------- .../x-privacy-manager/src/radio-btn.jsx | 30 --- components/x-privacy-manager/src/types.d.ts | 53 ---- components/x-privacy-manager/src/utils.js | 64 +++++ .../x-privacy-manager/storybook/data.js | 42 +-- .../x-privacy-manager/storybook/index.js | 22 ++ .../x-privacy-manager/storybook/knobs.js | 30 +++ .../storybook/story-legislation-ccpa.js | 13 + .../storybook/story-legislation-gdpr.js | 27 ++ components/x-privacy-manager/types.d.ts | 100 +++++++ 24 files changed, 867 insertions(+), 494 deletions(-) create mode 100644 components/x-privacy-manager/jsconfig.json create mode 100644 components/x-privacy-manager/src/__tests__/config.test.jsx create mode 100644 components/x-privacy-manager/src/__tests__/helpers.js create mode 100644 components/x-privacy-manager/src/__tests__/messaging.test.jsx delete mode 100644 components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx create mode 100644 components/x-privacy-manager/src/__tests__/state.test.jsx create mode 100644 components/x-privacy-manager/src/__tests__/utils.test.js create mode 100644 components/x-privacy-manager/src/actions.js create mode 100644 components/x-privacy-manager/src/components/form.jsx rename components/x-privacy-manager/src/{ => components}/messages.jsx (70%) create mode 100644 components/x-privacy-manager/src/components/radio-btn.jsx rename components/x-privacy-manager/src/{ => components}/radio-btn.scss (82%) delete mode 100644 components/x-privacy-manager/src/radio-btn.jsx delete mode 100644 components/x-privacy-manager/src/types.d.ts create mode 100644 components/x-privacy-manager/src/utils.js create mode 100644 components/x-privacy-manager/storybook/index.js create mode 100644 components/x-privacy-manager/storybook/knobs.js create mode 100644 components/x-privacy-manager/storybook/story-legislation-ccpa.js create mode 100644 components/x-privacy-manager/storybook/story-legislation-gdpr.js create mode 100644 components/x-privacy-manager/types.d.ts diff --git a/.eslintignore b/.eslintignore index 52079d084..114afbe73 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ +**/*.ts **/coverage/** **/node_modules/** **/bower_components/** diff --git a/components/x-privacy-manager/jsconfig.json b/components/x-privacy-manager/jsconfig.json new file mode 100644 index 000000000..75d668cf9 --- /dev/null +++ b/components/x-privacy-manager/jsconfig.json @@ -0,0 +1,4 @@ +{ + "include": ["src/**/*.js", "src/**/*.jsx", "./types.d.ts"], + "exclude": ["node_modules"] +} diff --git a/components/x-privacy-manager/package.json b/components/x-privacy-manager/package.json index e62242cfc..c1b46f0cb 100644 --- a/components/x-privacy-manager/package.json +++ b/components/x-privacy-manager/package.json @@ -11,6 +11,7 @@ "module": "dist/privacy-manager.esm.js", "browser": "dist/privacy-manager.es5.js", "style": "dist/privacy-manager.css", + "types": "types.d.ts", "repository": { "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" diff --git a/components/x-privacy-manager/src/__tests__/config.test.jsx b/components/x-privacy-manager/src/__tests__/config.test.jsx new file mode 100644 index 000000000..a095fb9a1 --- /dev/null +++ b/components/x-privacy-manager/src/__tests__/config.test.jsx @@ -0,0 +1,37 @@ +const { h } = require('@financial-times/x-engine'); +const { mount } = require('@financial-times/x-test-utils/enzyme'); + +import { defaultProps } from './helpers'; + +import { BasePrivacyManager } from '../privacy-manager'; + +describe('Config', () => { + it('renders the default UI', () => { + const subject = mount(<BasePrivacyManager {...defaultProps} />); + const labelTrue = subject.find('label[htmlFor="consent-true"]'); + const labelFalse = subject.find('label[htmlFor="consent-false"]'); + + expect(labelTrue.text()).toBe('Allow' + 'See personalised adverts'); + expect(labelFalse.text()).toBe('Block' + 'Opt out of personalised adverts'); + }); + + it('renders custom Button text', () => { + const buttonText = { + allow: { + label: 'Custom label', + text: 'Custom allow text', + }, + submit: { label: 'Custom save' }, + }; + const props = { ...defaultProps, buttonText }; + + const subject = mount(<BasePrivacyManager {...props} />); + const labelTrue = subject.find('label[htmlFor="consent-true"]'); + const labelFalse = subject.find('label[htmlFor="consent-false"]'); + const labelSave = subject.find('button[type="submit"]'); + + expect(labelTrue.text()).toBe('Custom label' + 'Custom allow text'); + expect(labelFalse.text()).toBe('Block' + 'Opt out of personalised adverts'); + expect(labelSave.text()).toBe('Custom save'); + }); +}); diff --git a/components/x-privacy-manager/src/__tests__/helpers.js b/components/x-privacy-manager/src/__tests__/helpers.js new file mode 100644 index 000000000..76378aa96 --- /dev/null +++ b/components/x-privacy-manager/src/__tests__/helpers.js @@ -0,0 +1,61 @@ +export const CONSENT_PROXY_HOST = 'https://consent.ft.com'; +export const CONSENT_PROXY_ENDPOINT = + 'https://consent.ft.com/__consent/consent-record/FTPINK/abcde'; + +export const buildPayload = (consent) => ({ + consentSource: 'consuming-app', + data: { + behaviouralAds: { + onsite: { + fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', + lbi: true, + source: 'consuming-app', + status: consent, + }, + }, + demographicAds: { + onsite: { + fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', + lbi: true, + source: 'consuming-app', + status: consent, + }, + }, + programmaticAds: { + onsite: { + fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', + lbi: true, + source: 'consuming-app', + status: consent, + }, + }, + }, + cookieDomain: '.ft.com', + formOfWordsId: 'privacyCCPA', +}); + +export function checkPayload(opts, expected) { + const consents = JSON.parse(String(opts.body)).data; + + let worked = true; + for (const category in consents) { + worked = worked && consents[category].onsite.status === expected; + } + return worked; +} + +export const defaultProps = { + userId: 'abcde', + consentProxyApiHost: CONSENT_PROXY_HOST, + consentSource: 'consuming-app', + referrer: 'www.ft.com', + cookieDomain: '.ft.com', + fow: { + id: 'privacyCCPA', + version: 'H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', + }, + actions: { + onConsentChange: jest.fn(() => {}), + sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }), + }, +}; diff --git a/components/x-privacy-manager/src/__tests__/messaging.test.jsx b/components/x-privacy-manager/src/__tests__/messaging.test.jsx new file mode 100644 index 000000000..eacf8e766 --- /dev/null +++ b/components/x-privacy-manager/src/__tests__/messaging.test.jsx @@ -0,0 +1,74 @@ +const { h } = require('@financial-times/x-engine'); +const { mount } = require('@financial-times/x-test-utils/enzyme'); + +import { defaultProps } from './helpers'; + +import { BasePrivacyManager } from '../privacy-manager'; + +function findMessageComponent(props) { + const subject = mount(<BasePrivacyManager {...props} />); + const messages = subject.find('[data-o-component="o-message"]'); + const message = messages.first(); + const link = message.find('[data-component="referrer-link"]'); + + return { + messages, + message, + link, + }; +} + +describe('x-privacy-manager', () => { + describe('Messaging', () => { + const messageProps = { + ...defaultProps, + consent: true, + legislation: ['ccpa'], + isLoading: false, + _response: undefined, + }; + + it('None by default', () => { + const { messages } = findMessageComponent(messageProps); + expect(messages).toHaveLength(0); + }); + + it('While loading', () => { + const { messages, message } = findMessageComponent({ ...messageProps, isLoading: true }); + expect(messages).toHaveLength(1); + expect(message).toHaveClassName('o-message--neutral'); + }); + + it('On receiving a response with a status of 200', () => { + const _response = { ok: true, status: 200 }; + const { messages, message, link } = findMessageComponent({ ...messageProps, _response }); + + expect(messages).toHaveLength(1); + expect(message).toHaveClassName('o-message--success'); + expect(link).toHaveProp('href', 'https://www.ft.com/'); + }); + + it('On receiving a response with a non-200 status', () => { + const _response = { ok: false, status: 400 }; + const { messages, message, link } = findMessageComponent({ ...messageProps, _response }); + + expect(messages).toHaveLength(1); + expect(message).toHaveClassName('o-message--error'); + expect(link).toHaveProp('href', 'https://www.ft.com/'); + }); + + it('On receiving any response with referrer undefined', () => { + const _response = { ok: false, status: 400 }; + const referrer = undefined; + const { messages, message, link } = findMessageComponent({ + ...messageProps, + referrer, + _response, + }); + + expect(messages).toHaveLength(1); + expect(message).toHaveClassName('o-message--error'); + expect(link).toHaveLength(0); + }); + }); +}); diff --git a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx b/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx deleted file mode 100644 index 382a6c883..000000000 --- a/components/x-privacy-manager/src/__tests__/privacy-manager.test.jsx +++ /dev/null @@ -1,209 +0,0 @@ -const { h } = require('@financial-times/x-engine') -const { mount } = require('@financial-times/x-test-utils/enzyme') -const fetchMock = require('fetch-mock') - -import { BasePrivacyManager, PrivacyManager } from '../privacy-manager' - -const TEST_CONSENT_URL = 'https://consent.ft.com' - -const buildPayload = (consent) => ({ - consentSource: 'consuming-app', - data: { - behaviouralAds: { - onsite: { - fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', - lbi: true, - source: 'consuming-app', - status: consent - } - }, - demographicAds: { - onsite: { - fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', - lbi: true, - source: 'consuming-app', - status: consent - } - }, - programmaticAds: { - onsite: { - fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', - lbi: true, - source: 'consuming-app', - status: consent - } - } - }, - cookieDomain: '.ft.com', - formOfWordsId: 'privacyCCPA' -}) - -function checkPayload(opts, expected) { - const consents = JSON.parse(String(opts.body)).data - - let worked = true - for (const category in consents) { - worked = worked && consents[category].onsite.status === expected - } - return worked -} - -const defaultProps = { - consentProxyEndpoints: { - core: TEST_CONSENT_URL, - enhanced: TEST_CONSENT_URL, - createOrUpdateRecord: TEST_CONSENT_URL - }, - consentSource: 'consuming-app', - referrer: 'www.ft.com', - cookieDomain: '.ft.com' -} - -describe('x-privacy-manager', () => { - describe('initial state', () => { - beforeEach(() => { - fetchMock.reset() - const okResponse = { - body: { a: 'b' }, - status: 200 - } - fetchMock.mock(TEST_CONSENT_URL, okResponse, { delay: 500 }) - }) - - it('defaults to "Allow"', () => { - const subject = mount(<PrivacyManager {...defaultProps} />) - const inputTrue = subject.find('input[value="true"]').first() - - // Verify that initial props are correctly reflected - expect(inputTrue.prop('checked')).toBe(true) - }) - - it('highlights explicitly set consent correctly: false', () => { - const subject = mount(<PrivacyManager {...defaultProps} consent={false} />) - const inputFalse = subject.find('input[value="false"]').first() - - // Verify that initial props are correctly reflected - expect(inputFalse.prop('checked')).toBe(true) - }) - - it('highlights explicitly set consent correctly: true', () => { - const subject = mount(<PrivacyManager consent={true} {...defaultProps} />) - const inputTrue = subject.find('input[value="true"]').first() - - // Verify that initial props are correctly reflected - expect(inputTrue.prop('checked')).toBe(true) - }) - - it('handles a change of consent', async () => { - const callback1 = jest.fn() - const callback2 = jest.fn() - const consentVal = true - - const props = { ...defaultProps, onConsentSavedCallbacks: [callback1, callback2] } - - const payload = buildPayload(consentVal) - - const subject = mount(<PrivacyManager {...props} />) - const form = subject.find('form').first() - const inputTrue = subject.find('input[value="true"]').first() - const inputFalse = subject.find('input[value="false"]').first() - - // Switch consent to false and submit form - await inputFalse.prop('onChange')(undefined) - await form.prop('onSubmit')(undefined) - - // Reconcile snapshot with state - subject.update() - - // Check that fetch was called with the correct values - expect(checkPayload(fetchMock.lastOptions(), false)).toBe(true) - - // Switch consent back to true and resubmit form - await inputTrue.prop('onChange')(undefined) - await form.prop('onSubmit')(undefined) - - // Check both callbacks were run with `payload` - expect(callback1).toHaveBeenCalledWith(null, { payload, consent: true }) - expect(callback2).toHaveBeenCalledWith(null, { payload, consent: true }) - - // Reconcile snapshot with state - subject.update() - - // Check that fetch was called with the correct values - expect(checkPayload(fetchMock.lastOptions(), true)).toBe(true) - - // Verify that confimatory nmessage is displayed - const message = subject.find('[data-o-component="o-message"]').first() - const link = message.find('[data-component="referrer-link"]') - expect(message).toHaveClassName('o-message--success') - expect(link).toHaveProp('href', 'https://www.ft.com/') - expect(inputTrue).toHaveProp('checked', true) - }) - }) - - describe('It displays the appropriate messaging', () => { - function findMessageComponent(props) { - const subject = mount(<BasePrivacyManager {...props} />) - const messages = subject.find('[data-o-component="o-message"]') - const message = messages.first() - const link = message.find('[data-component="referrer-link"]') - - return { - messages, - message, - link - } - } - - const messageProps = { - ...defaultProps, - consent: true, - legislation: ['ccpa'], - actions: { - onConsentChange: jest.fn(() => {}), - sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }) - }, - isLoading: false, - _response: undefined - } - - it('None by default', () => { - const { messages } = findMessageComponent(messageProps) - expect(messages).toHaveLength(0) - }) - - it('While loading', () => { - const { messages, message } = findMessageComponent({ ...messageProps, isLoading: true }) - expect(messages).toHaveLength(1) - expect(message).toHaveClassName('o-message--neutral') - }) - - it('On receiving a response with a status of 200', () => { - const _response = { ok: true, status: 200 } - const { messages, message, link } = findMessageComponent({ ...messageProps, _response }) - - expect(messages).toHaveLength(1) - expect(message).toHaveClassName('o-message--success') - expect(link).toHaveProp('href', 'https://www.ft.com/') - }) - - it('On receiving a response with a non-200 status', () => { - const _response = { ok: false, status: 400 } - const { messages, message, link } = findMessageComponent({ ...messageProps, _response }) - - expect(messages).toHaveLength(1) - expect(message).toHaveClassName('o-message--error') - expect(link).toHaveProp('href', 'https://www.ft.com/') - }) - - it('On receiving any response with referrer undefined', () => { - const _response = { ok: false, status: 400 } - const referrer = undefined - const { messages, message, link } = findMessageComponent({ ...messageProps, referrer, _response }) - - expect(messages).toHaveLength(1) - expect(message).toHaveClassName('o-message--error') - expect(link).toHaveLength(0) - }) - }) -}) diff --git a/components/x-privacy-manager/src/__tests__/state.test.jsx b/components/x-privacy-manager/src/__tests__/state.test.jsx new file mode 100644 index 000000000..12d788291 --- /dev/null +++ b/components/x-privacy-manager/src/__tests__/state.test.jsx @@ -0,0 +1,88 @@ +const { h } = require('@financial-times/x-engine'); +const { mount } = require('@financial-times/x-test-utils/enzyme'); +const fetchMock = require('fetch-mock'); + +import * as helpers from './helpers'; + +import { PrivacyManager } from '../privacy-manager'; + +const checkInput = ({ consent }) => () => { + const props = { ...helpers.defaultProps, consent }; + const subject = mount(<PrivacyManager {...props} />); + const input = subject.find(`input[value="${consent}"]`).first(); + + // Verify that initial props are correctly reflected + expect(input.prop('checked')).toBe(true); +}; + +describe('x-privacy-manager', () => { + describe('Messaging', () => { + beforeEach(() => { + fetchMock.reset(); + const okResponse = { + body: { a: 'b' }, + status: 200, + }; + + const targetUrl = helpers.CONSENT_PROXY_ENDPOINT + fetchMock.mock(targetUrl, okResponse, { delay: 500 }); + }); + + it('defaults to "undefined"', () => { + const subject = mount(<PrivacyManager {...helpers.defaultProps} />); + subject.find('input').forEach((input) => expect(input.prop('checked')).toBe(false)); + }); + + it('highlights explicitly set consent correctly: false', checkInput({ consent: false })); + it('highlights explicitly set consent correctly: true', checkInput({ consent: true })); + + it('handles a change of consent', async () => { + const callback1 = jest.fn(); + const callback2 = jest.fn(); + const consentVal = true; + + const props = { + ...helpers.defaultProps, + consent: true, + onConsentSavedCallbacks: [callback1, callback2], + }; + const payload = helpers.buildPayload(consentVal); + + const subject = mount(<PrivacyManager {...props} />); + const form = subject.find('form').first(); + const inputTrue = subject.find('input[value="true"]').first(); + const inputFalse = subject.find('input[value="false"]').first(); + + // Switch consent to false and submit form + await inputFalse.prop('onChange')(undefined); + await form.prop('onSubmit')(undefined); + + // Reconcile snapshot with state + await subject.update(); + + // Check that fetch was called with the correct values + expect(helpers.checkPayload(fetchMock.lastOptions(), false)).toBe(true); + + // Switch consent back to true and resubmit form + await inputTrue.prop('onChange')(undefined); + await form.prop('onSubmit')(undefined); + + // Reconcile snapshot with state + await subject.update(); + + // Check both callbacks were run with `payload` + expect(callback1).toHaveBeenCalledWith(null, { payload, consent: true }); + expect(callback2).toHaveBeenCalledWith(null, { payload, consent: true }); + + // Check that fetch was called with the correct values + expect(helpers.checkPayload(fetchMock.lastOptions(), true)).toBe(true); + + // Verify that confimatory nmessage is displayed + const message = subject.find('[data-o-component="o-message"]').first(); + const link = message.find('[data-component="referrer-link"]'); + // expect(message).toHaveClassName('o-message--success'); + expect(link).toHaveProp('href', 'https://www.ft.com/'); + expect(inputTrue).toHaveProp('checked', true); + }); + }); +}); diff --git a/components/x-privacy-manager/src/__tests__/utils.test.js b/components/x-privacy-manager/src/__tests__/utils.test.js new file mode 100644 index 000000000..519ca750a --- /dev/null +++ b/components/x-privacy-manager/src/__tests__/utils.test.js @@ -0,0 +1,48 @@ +const { getTrackingKeys, getConsentProxyEndpoints } = require('../utils'); + +describe('getTrackingKeys', () => { + it('Creates legislation-specific tracking event names', () => { + expect(getTrackingKeys('ccpa')).toEqual({ + 'advertising-toggle-block': 'ccpa-advertising-toggle-block', + 'advertising-toggle-allow': 'ccpa-advertising-toggle-allow', + 'consent-allow': 'ccpa-consent-allow', + 'consent-block': 'ccpa-consent-block', + }); + }); +}); + +describe('getConsentProxyEndpoints', () => { + const params = { + userId: 'abcde', + consentProxyApiHost: 'https://consent.ft.com', + cookieDomain: '.ft.com', + }; + + const defaultEndpoint = 'https://consent.ft.com/__consent/consent-record-cookie'; + + it('generates endpoints for logged-in users', () => { + expect(getConsentProxyEndpoints(params)).toEqual({ + core: `https://consent.ft.com/__consent/consent-record/FTPINK/abcde`, + enhanced: `https://consent.ft.com/__consent/consent/FTPINK/abcde`, + createOrUpdateRecord: `https://consent.ft.com/__consent/consent-record/FTPINK/abcde`, + }); + }); + + it('generates endpoints for logged-out users', () => { + const loggedOutParams = { ...params, userId: undefined }; + expect(getConsentProxyEndpoints(loggedOutParams)).toEqual({ + core: defaultEndpoint, + enhanced: defaultEndpoint, + createOrUpdateRecord: defaultEndpoint, + }); + }); + + it('generates endpoints for cookie-only circumstances', () => { + const loggedOutParams = { ...params, cookiesOnly: true }; + expect(getConsentProxyEndpoints(loggedOutParams)).toEqual({ + core: defaultEndpoint, + enhanced: defaultEndpoint, + createOrUpdateRecord: defaultEndpoint, + }); + }); +}); diff --git a/components/x-privacy-manager/src/actions.js b/components/x-privacy-manager/src/actions.js new file mode 100644 index 000000000..442688f79 --- /dev/null +++ b/components/x-privacy-manager/src/actions.js @@ -0,0 +1,83 @@ +import { withActions } from '@financial-times/x-interaction' + +function onConsentChange(consent) { + return () => ({ consent }) +} + +/** + * Save the users choice via the ConsentProxy + * - consentSource: (e.g. 'next-control-centre') + * - cookieDomain: (e.g. '.thebanker.com') + * + * @param {import("../types").SendConsentProps} args + * @returns {({ isLoading, consent }: { isLoading: boolean, consent: boolean }) => Promise<{_response: _Response}>} + */ +function sendConsent({ consentApiUrl, onConsentSavedCallbacks, consentSource, cookieDomain, fow }) { + return async ({ isLoading, consent }) => { + if (isLoading) return + + const categoryPayload = { + onsite: { + status: consent, + lbi: true, + source: consentSource, + fow: `${fow.id}/${fow.version}` + } + } + + const payload = { + formOfWordsId: fow.id, + consentSource, + data: { + behaviouralAds: categoryPayload, + demographicAds: categoryPayload, + programmaticAds: categoryPayload + } + } + + if (cookieDomain) { + // Optionally specify the domain for the cookie consent api will set + payload.cookieDomain = cookieDomain + } + + // eslint-disable-next-line + console.log({ payload }) + + try { + const res = await fetch(consentApiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload), + credentials: 'include' + }) + + // On response call any externally defined handlers following Node's convention: + // 1. Either an error object or `null` as the first argument + // 2. An object containing `consent` and `payload` as the second + // Allows callbacks to decide how to handle a failure scenario + + if (res.ok === false) { + throw new Error(res.statusText || String(res.status)) + } + + for (const fn of onConsentSavedCallbacks) { + fn(null, { consent, payload }) + } + + return { _response: { ok: true } } + } catch (err) { + for (const fn of onConsentSavedCallbacks) { + fn(err, { consent, payload }) + } + + return { _response: { ok: false } } + } + } +} + +export const withCustomActions = withActions(() => ({ + onConsentChange, + sendConsent +})) diff --git a/components/x-privacy-manager/src/components/form.jsx b/components/x-privacy-manager/src/components/form.jsx new file mode 100644 index 000000000..a75481946 --- /dev/null +++ b/components/x-privacy-manager/src/components/form.jsx @@ -0,0 +1,33 @@ +import { h } from '@financial-times/x-engine'; +import s from '../privacy-manager.scss'; + +/** + * @param {import("../../types").FormProps} args + */ +export const Form = ({ + consent, + consentApiUrl, + sendConsent, + trackingKeys, + buttonText, + children, +}) => { + /** @type {import('../../types').TrackingKey} */ + const consentAction = consent ? 'consent-allow' : 'consent-block'; + const btnTrackingId = trackingKeys[consentAction]; + + /** @param {React.FormEvent} event */ + const onSubmit = (event) => { + event && event.preventDefault(); + return sendConsent(); + }; + + return ( + <form action={consentApiUrl} onSubmit={onSubmit}> + <div className={s.form__controls}>{children}</div> + <button className={s.form__submit} type="submit" data-trackable={btnTrackingId}> + {buttonText.submit.label} + </button> + </form> + ); +}; diff --git a/components/x-privacy-manager/src/messages.jsx b/components/x-privacy-manager/src/components/messages.jsx similarity index 70% rename from components/x-privacy-manager/src/messages.jsx rename to components/x-privacy-manager/src/components/messages.jsx index 31db67d2d..4937cc7af 100644 --- a/components/x-privacy-manager/src/messages.jsx +++ b/components/x-privacy-manager/src/components/messages.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine' -import s from './privacy-manager.scss' +import s from '../privacy-manager.scss' /** * Provide a way to return to the referrer's homepage @@ -79,3 +79,29 @@ export function LoadingMessage() { </Message> ) } + +/** + * @param {boolean} isLoading + * @param {_Response} response + * @param {string} referrer + */ +export function renderMessage(isLoading, response, referrer) { + if (isLoading) return <LoadingMessage /> + if (response) return <ResponseMessage success={response.ok} referrer={referrer} /> + return null +} + +/** + * Display a warning to users + * @param {string} [userId] + */ +export function renderLoggedOutWarning(userId) { + if (userId) return null + + return ( + <p className={`${s.consent__copy} ${s['consent__copy--cta']}`} data-component="login-cta"> + Please sign into your account before submitting your preferences to ensure these changes are applied + across all of your devices + </p> + ) +} diff --git a/components/x-privacy-manager/src/components/radio-btn.jsx b/components/x-privacy-manager/src/components/radio-btn.jsx new file mode 100644 index 000000000..3e709402f --- /dev/null +++ b/components/x-privacy-manager/src/components/radio-btn.jsx @@ -0,0 +1,47 @@ +import { h } from '@financial-times/x-engine' + +import s from './radio-btn.scss' + +/** + * @param {{ + * name: string, + * type: "allow" | "block", + * checked: boolean, + * trackingKeys: Record<string, string>, + * onChange: () => void, + * children?: Element + * }} args + * + * @returns {JSX.Element} + */ +export function RadioBtn({ name, type, checked, trackingKeys, buttonText, onChange }) { + const value = type === 'allow' + const id = `${name}-${value}` + const trackingId = trackingKeys[`advertising-toggle-${value}`] + + return ( + <div className={s.control}> + <input + className={s.input} + id={id} + type="radio" + name={name} + value={value.toString()} + checked={checked} + data-trackable={trackingId} + onChange={() => onChange(value)} + /> + <label htmlFor={id} className={s.label}> + <span className={s.label__text}> + <strong>{buttonText[type].label}</strong> + <span>{buttonText[type].text}</span> + </span> + + <svg className={s.label__icon} viewBox="0 0 36 36" aria-hidden="true" focusable="false"> + <circle className={s.label__icon__outer} cx="18" cy="18" r="16" /> + <circle className={s.label__icon__inner} cx="18" cy="18" r="8" /> + </svg> + </label> + </div> + ) +} diff --git a/components/x-privacy-manager/src/radio-btn.scss b/components/x-privacy-manager/src/components/radio-btn.scss similarity index 82% rename from components/x-privacy-manager/src/radio-btn.scss rename to components/x-privacy-manager/src/components/radio-btn.scss index fe1b4ce38..f3cbf02fe 100644 --- a/components/x-privacy-manager/src/radio-btn.scss +++ b/components/x-privacy-manager/src/components/radio-btn.scss @@ -4,6 +4,8 @@ @import 'o-spacing/main'; @import 'o-typography/main'; +$transitionDuration: 0.1s; + .input { @include oNormaliseVisuallyHidden; } @@ -22,7 +24,8 @@ } .label { - transition: background-color 0.1s ease-in, color 0.1s ease-in; + transition: background-color, color; + transition-duration: $transitionDuration; display: flex; align-items: center; @@ -79,14 +82,26 @@ } .label__icon__outer { + transition: stroke $transitionDuration; + stroke: currentColor; stroke-width: 3px; fill: transparent; + + .input:not(:checked) + .label:hover & { + stroke: oColorsByName('teal-40'); + } } .label__icon__inner { + transition: fill $transitionDuration; + fill: transparent; + .input:not(:checked) + .label:hover & { + fill: oColorsByName('teal-40'); + } + .input:checked + .label & { fill: currentColor; } diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx index dd73744e5..021327ee1 100644 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -1,207 +1,88 @@ -/// <reference path="./types.d.ts" /> - import { h } from '@financial-times/x-engine' -import { withActions } from '@financial-times/x-interaction' +import { renderLoggedOutWarning, renderMessage } from './components/messages' +import { Form } from './components/form' +import { RadioBtn } from './components/radio-btn' +import { withCustomActions } from './actions' +import * as utils from './utils' import s from './privacy-manager.scss' -import { RadioBtn } from './radio-btn' -import { LoadingMessage, ResponseMessage } from './messages' - -// CCPA legislation doesn't require to record the form-of-words used in the page, -// but our consent-proxy schemas do require the field. For that reason, -// it was decided to create a placeholder in our FOW database that would always use -// for CCPA page independently of the specific wording used in it. -// This is the value: -const FOW_NAME = 'privacyCCPA' -const FOW_VERSION = 'H0IeyQBalorD.6nTqqzhNTKECSgOPJCG' - -export const withCustomActions = withActions(() => ({ - onConsentChange() { - return ({ consent = true }) => ({ consent: !consent }) - }, - - /** - * Save the users choice via the ConsentProxy - * - * @param {string} consentApiEnhancedUrl - * @param {OnSaveCallback[]} onConsentSavedCallbacks - * @param {string} consentSource (e.g. 'next-control-centre') - * @param {string | undefined} cookieDomain (e.g. '.thebanker.com') - * - * @returns {(props: BasePrivacyManagerProps) => Promise<{_response: _Response}>} - */ - sendConsent(consentApiEnhancedUrl, onConsentSavedCallbacks, consentSource, cookieDomain) { - return async ({ isLoading, consent }) => { - if (isLoading) return - - const categoryPayload = { - onsite: { - status: consent, - lbi: true, - source: consentSource, - fow: `${FOW_NAME}/${FOW_VERSION}` - } - } - - const payload = { - formOfWordsId: FOW_NAME, - consentSource, - data: { - behaviouralAds: categoryPayload, - demographicAds: categoryPayload, - programmaticAds: categoryPayload - } - } - - if (cookieDomain) { - // Optionally specifiy the domain for the cookie consent api will set - payload.cookieDomain = cookieDomain - } - - try { - const res = await fetch(consentApiEnhancedUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(payload), - credentials: 'include' - }) - // On response call any externally defined handlers following Node's convention: - // 1. Either an error object or `null` as the first argument - // 2. An object containing `consent` and `payload` as the second - // Allows callbacks to decide how to handle a failure scenario - - if (res.ok === false) { - throw new Error(res.statusText || String(res.status)) - } - - for (const fn of onConsentSavedCallbacks) { - fn(null, { consent, payload }) - } - - return { _response: { ok: true } } - } catch (err) { - for (const fn of onConsentSavedCallbacks) { - fn(err, { consent, payload }) - } - - return { _response: { ok: false } } - } - } - } -})) - -/** - * @param {boolean} isLoading - * @param {_Response} response - * @param {string} referrer - */ -function renderMessage(isLoading, response, referrer) { - if (isLoading) return <LoadingMessage /> - if (response) return <ResponseMessage success={response.ok} referrer={referrer} /> - return null +const defaultButtonText = { + allow: { label: 'Allow', text: 'See personalised adverts' }, + block: { label: 'Block', text: 'Opt out of personalised adverts' }, + submit: { label: 'Save' } } /** - * Display a warning to users - * @param {string | undefined} userId + * @param {import('../types').BasePrivacyManagerProps} Props */ -function renderLoggedOutWarning(userId) { - if (userId && userId.length > 0) return null - - return ( - <p className={`${s.consent__copy} ${s['consent__copy--cta']}`}> - Please sign into your account before submitting your preferences to ensure these changes are applied - across all of your devices - </p> - ) -} - -/** - * @param {BasePrivacyManagerProps} Props - */ - export function BasePrivacyManager({ userId, - consent = true, - consentProxyEndpoints, - consentSource, - onConsentSavedCallbacks = [], referrer, + legislationId, cookieDomain, + fow, + consent, + consentSource, + consentProxyApiHost, + buttonText = {}, + onConsentSavedCallbacks = [], actions, isLoading, + children, _response = undefined }) { - const trackingAction = consent ? 'allow' : 'block' - const btnTrackingId = `ccpa-advertising-consent-${trackingAction}` + // Shallowly merge supplied button labels with defaults + buttonText = { ...defaultButtonText, ...buttonText } + + const consentProxyEndpoints = utils.getConsentProxyEndpoints({ + userId, + consentProxyApiHost, + cookieDomain + }) + const consentApiUrl = consentProxyEndpoints.createOrUpdateRecord + const trackingKeys = utils.getTrackingKeys(legislationId) + const { sendConsent, onConsentChange } = actions + + const radioBtnProps = (type, checked) => ({ + name: 'consent', + type, + checked, + trackingKeys, + buttonText, + onChange: onConsentChange + }) + + /** @type {import('../types').FormProps} */ + const formProps = { + consent, + consentApiUrl, + trackingKeys, + buttonText, + sendConsent: () => { + return sendConsent({ + consentApiUrl, + onConsentSavedCallbacks, + consentSource, + cookieDomain, + fow + }) + } + } return ( - <div className={s.consent}> - <h1 className={s.consent__title}>Do Not Sell My Personal Information</h1> - <div className={s.consent__copy}> - <p> - If you are a California resident, the California Consumer Privacy Act (CCPA) provides you with a - right to opt out of the sale of your personal information. The definition of sale is extremely broad - under the CCPA, and may include sharing certain pieces of information with our advertising partners, - such as cookie identifiers, geolocation and interactions with advertisements, for the purposes of - showing you advertising that is relevant to your interests. You can find more information about this - in our <a href="https://help.ft.com/legal-privacy/privacy-policy/">Privacy Policy</a>, including - other ways to opt out. - </p> - - <p> - You can choose to block sharing of this data with advertisers. This means that we turn off some - types of advertising based on information you have given us and your use of our Sites, ensuring that - our advertising partners do not receive this data. By opting out, you will stop receiving adverts - that are targeted specifically to you; however, you will still see the same number of adverts on our - Sites. - </p> - <hr className={s.divider} /> - {renderLoggedOutWarning(userId)} - <div className={s.messages} aria-live="polite"> - {renderMessage(isLoading, _response, referrer)} - </div> - <form - action={consentProxyEndpoints.createOrUpdateRecord} - onSubmit={(event) => { - event && event.preventDefault() - return actions.sendConsent( - consentProxyEndpoints.createOrUpdateRecord, - onConsentSavedCallbacks, - consentSource, - cookieDomain - ) - }}> - <div className={s.form__controls}> - <RadioBtn - value="true" - trackingId="ccpa-advertising-toggle-allow" - checked={consent === true} - onChange={actions.onConsentChange}> - <strong>Allow</strong> - <span>See personalised adverts</span> - </RadioBtn> - <RadioBtn - value="false" - trackingId="ccpa-advertising-toggle-block" - checked={consent === false} - onChange={actions.onConsentChange}> - <strong>Block</strong> - <span>Opt out of personalised adverts</span> - </RadioBtn> - </div> - <button className={s.form__submit} type="submit" data-trackable={btnTrackingId}> - Save - </button> - </form> + <div className={s.consent} data-component="x-privacy-manager"> + {children} + {renderLoggedOutWarning(userId)} + <div className={s.messages} aria-live="polite"> + {renderMessage(isLoading, _response, referrer)} </div> + <Form {...formProps}> + <RadioBtn {...radioBtnProps('allow', consent === true)} /> + <RadioBtn {...radioBtnProps('block', consent === false)} type="block" checked={consent === false} /> + </Form> </div> ) } -const PrivacyManager = withCustomActions(BasePrivacyManager) - -export { PrivacyManager } +export const PrivacyManager = withCustomActions(BasePrivacyManager) diff --git a/components/x-privacy-manager/src/radio-btn.jsx b/components/x-privacy-manager/src/radio-btn.jsx deleted file mode 100644 index ac6fbaa06..000000000 --- a/components/x-privacy-manager/src/radio-btn.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import { h } from '@financial-times/x-engine' - -import s from './radio-btn.scss' - -export function RadioBtn({ value, trackingId, checked, onChange, children }) { - const id = `ccpa-${value}` - - return ( - <div className={s.control}> - <input - className={s.input} - id={id} - type="radio" - name="consent" - value={value} - checked={checked} - onChange={onChange} - data-trackable={trackingId} - /> - <label htmlFor={id} className={s.label}> - <span className={s.label__text}>{children}</span> - - <svg className={s.label__icon} viewBox="0 0 36 36" aria-hidden="true" focusable="false"> - <circle className={s.label__icon__outer} cx="18" cy="18" r="16" /> - <circle className={s.label__icon__inner} cx="18" cy="18" r="8" /> - </svg> - </label> - </div> - ) -} diff --git a/components/x-privacy-manager/src/types.d.ts b/components/x-privacy-manager/src/types.d.ts deleted file mode 100644 index bbb86a230..000000000 --- a/components/x-privacy-manager/src/types.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -type CategoryPayload = { - onsite: { - status: boolean - lbi: boolean - source: string - fow: string - } -} - -type CCPAConsentData = Record<'behaviouralAds' | 'demographicAds' | 'programmaticAds', CategoryPayload> - -type CCPAConsentPayload = { - formOfWordsId: string - consentSource: string - data: CCPAConsentData -} - -type OnSaveCallback = (err: null | Error, data: { consent: boolean; payload: CCPAConsentPayload }) => void - -type Actions = { - onConsentChange: () => void - sendConsent: ( - consentApiUrl: string, - onConsentSavedCallbacks: OnSaveCallback[], - consentSource: string - ) => Promise<{ _response: _Response }> -} - -type _Response = { - ok: boolean - status?: number -} - -type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string> - -type ConsentProxyEndpoints = { [key in keyof ConsentProxyEndpoint]: string } - -type PrivacyManagerProps = { - userId: string | undefined - consent?: boolean - referrer?: string - cookieDomain?: string - legislation?: string[] - consentSource: string - consentProxyEndpoints: ConsentProxyEndpoints - onConsentSavedCallbacks?: OnSaveCallback[] -} - -type BasePrivacyManagerProps = PrivacyManagerProps & { - actions: Actions - isLoading?: boolean - _response: _Response -} diff --git a/components/x-privacy-manager/src/utils.js b/components/x-privacy-manager/src/utils.js new file mode 100644 index 000000000..3396364d1 --- /dev/null +++ b/components/x-privacy-manager/src/utils.js @@ -0,0 +1,64 @@ +/** @type {import("../types").TrackingKey[]} */ +const trackingKeys = [ + 'advertising-toggle-block', + 'advertising-toggle-allow', + 'consent-allow', + 'consent-block' +] + +/** + * Create a look-up table legislationId-specific tracking event names + * e.g. { 'advertising-toggle-block': 'gdpr-advertising-toggle-block' } + * + * @param {string} legislationId + * @returns {Record<import("../types").TrackingKey, string>} + */ +export function getTrackingKeys(legislationId) { + /** @type Record<TrackingKey, string> */ + const dict = {} + for (const key of trackingKeys) { + dict[key] = `${legislationId}-${key}` + } + + return dict +} + +/** + * @param {{ + * userId: string; + * consentProxyApiHost: string; + * cookiesOnly?: boolean; + * cookieDomain?: string; + * }} param + * @returns {import("../types").ConsentProxyEndpoint} + */ +export function getConsentProxyEndpoints({ + userId, + consentProxyApiHost, + cookiesOnly = false, + cookieDomain = '' +}) { + if (cookieDomain.length > 0) { + // Override the domain so that set-cookie headers in consent api responses are respected + consentProxyApiHost = consentProxyApiHost.replace('.ft.com', cookieDomain) + } + + const endpointDefault = `${consentProxyApiHost}/__consent/consent-record-cookie` + + if (userId && !cookiesOnly) { + const endpointCore = `${consentProxyApiHost}/__consent/consent-record/FTPINK/${userId}` + const endpointEnhanced = `${consentProxyApiHost}/__consent/consent/FTPINK/${userId}` + + return { + core: endpointCore, + enhanced: endpointEnhanced, + createOrUpdateRecord: endpointCore + } + } + + return { + core: endpointDefault, + enhanced: endpointDefault, + createOrUpdateRecord: endpointDefault + } +} diff --git a/components/x-privacy-manager/storybook/data.js b/components/x-privacy-manager/storybook/data.js index f321aad63..12a96e1d7 100644 --- a/components/x-privacy-manager/storybook/data.js +++ b/components/x-privacy-manager/storybook/data.js @@ -1,4 +1,4 @@ -const CONSENT_API = 'https://consent.ft.com' +const CONSENT_API = 'https://mock-consent.ft.com' const referrers = { 'ft.com': 'www.ft.com', @@ -19,19 +19,29 @@ const referrers = { Default: '' } -const legislation = { - CCPA: ['ccpa', 'gdpr'] -} - const defaultArgs = { userId: 'fakeUserId', - consent: undefined, - legislation: 'ccpa', - referrer: 'www.ft.com', - consentProxyEndpoints: { - core: CONSENT_API, - enhanced: CONSENT_API, - createOrUpdateRecord: CONSENT_API + legislationId: 'ccpa', + consent: true, + referrer: 'ft.com', + fow: { + id: 'privacyCCPA', + version: 'H0IeyQBalorD.6nTqqzhNTKECSgOPJCG' + }, + consentSource: 'next-control-centre', + consentProxyApiHost: CONSENT_API, + buttonText: { + allow: { + label: 'Allow', + text: 'See personalised adverts' + }, + block: { + label: 'Block', + text: 'Opt out of personalised adverts' + }, + submit: { + label: 'Save' + } } } @@ -40,14 +50,14 @@ const defaultArgTypes = { name: 'Authentication', control: { type: 'select', options: { loggedIn: defaultArgs.userId, loggedOut: undefined } } }, - legislation: { control: { type: 'select', options: legislation['CCPA'] } }, referrer: { control: { type: 'select', options: referrers } }, consent: { control: { type: 'boolean' }, name: 'consent' } } -const fetchMock = (fetchMock, status = 200) => { - fetchMock.restore().mock(CONSENT_API, status, { - delay: 1000 +const fetchMock = (status = 200, options = {}) => (fetchMock) => { + fetchMock.mock('https://mock-consent.ft.com/__consent/consent-record/FTPINK/fakeUserId', status, { + delay: 1000, + ...options }) } diff --git a/components/x-privacy-manager/storybook/index.js b/components/x-privacy-manager/storybook/index.js new file mode 100644 index 000000000..25bc007e3 --- /dev/null +++ b/components/x-privacy-manager/storybook/index.js @@ -0,0 +1,22 @@ +const { PrivacyManager } = require('../src/privacy-manager') + +exports.component = PrivacyManager + +exports.package = require('../package.json') + +exports.dependencies = { + 'o-loading': '^4.0.0', + 'o-message': '^4.0.0', + 'o-typography': '^6.0.0' +} + +exports.stories = [ + require('./story-legislation-ccpa'), + require('./story-legislation-gdpr'), + require('./story-consent-indeterminate'), + require('./story-consent-accepted'), + require('./story-consent-blocked'), + require('./story-save-failed') +] + +exports.knobs = require('./knobs') diff --git a/components/x-privacy-manager/storybook/knobs.js b/components/x-privacy-manager/storybook/knobs.js new file mode 100644 index 000000000..618246527 --- /dev/null +++ b/components/x-privacy-manager/storybook/knobs.js @@ -0,0 +1,30 @@ +const referrers = { + 'ft.com': 'www.ft.com', + 'exec-appointments.com': 'www.exec-appointments.com', + 'fdibenchmark.com': 'www.fdibenchmark.com', + 'fdiintelligence.com': 'www.fdiintelligence.com', + 'fdimarkets.com': 'www.fdimarkets.com', + 'fdireports.com': 'www.fdireports.com', + 'ftadviser.com': 'www.ftadviser.com', + 'ftconfidentialresearch.com': 'www.ftconfidentialresearch.com', + 'globalriskregulator.com': 'www.globalriskregulator.com', + 'investorschronicle.co.uk': 'www.investorschronicle.co.uk', + 'non-execs.com': 'www.non-execs.com', + 'pensions-expert.com': 'www.pensions-expert.com', + 'pwmnet.com': 'www.pwmnet.com', + 'thebanker.com': 'www.thebanker.com', + 'thebankerdatabase.com': 'www.thebankerdatabase.com', + Undefined: '' +} + +module.exports = (data, { boolean, select }) => ({ + userId() { + return boolean('Authenticated', data.userId, undefined); + }, + consent() { + return boolean('Consent', data.consent, undefined) + }, + referrer() { + return select('Referrer', referrers, referrers['ft.com']) + } +}) diff --git a/components/x-privacy-manager/storybook/story-legislation-ccpa.js b/components/x-privacy-manager/storybook/story-legislation-ccpa.js new file mode 100644 index 000000000..b354b1d60 --- /dev/null +++ b/components/x-privacy-manager/storybook/story-legislation-ccpa.js @@ -0,0 +1,13 @@ +const { defaults, getFetchMock } = require('./data') + +exports.title = 'Consent CCPA' + +exports.data = { ...defaults, consent: true } + +exports.knobs = Object.keys(exports.data) + +exports.fetchMock = getFetchMock() + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module diff --git a/components/x-privacy-manager/storybook/story-legislation-gdpr.js b/components/x-privacy-manager/storybook/story-legislation-gdpr.js new file mode 100644 index 000000000..819be7079 --- /dev/null +++ b/components/x-privacy-manager/storybook/story-legislation-gdpr.js @@ -0,0 +1,27 @@ +const { defaults, getFetchMock } = require('./data') + +exports.title = 'Consent GDPR' + +exports.data = { + ...defaults, + legislationId: 'gdpr', + consent: undefined, + buttonText: { + allow: { + label: 'Allow', + text: 'See personalised advertising and allow measurement of advertising effectiveness' + }, + block: { + label: 'Block', + text: 'Block personalised advertising and measurement of advertising effectiveness' + } + } +} + +exports.knobs = Object.keys(exports.data) + +exports.fetchMock = getFetchMock() + +// This reference is only required for hot module loading in development +// <https://webpack.js.org/concepts/hot-module-replacement/> +exports.m = module diff --git a/components/x-privacy-manager/types.d.ts b/components/x-privacy-manager/types.d.ts new file mode 100644 index 000000000..f1a9e1933 --- /dev/null +++ b/components/x-privacy-manager/types.d.ts @@ -0,0 +1,100 @@ +declare module '*.scss' { + const content: { [className: string]: string }; + export default content; +} + +type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string>; +type ConsentProxyEndpoints = Partial<{ [key in keyof ConsentProxyEndpoint]: string }>; + +type TrackingKey = + | 'advertising-toggle-allow' + | 'advertising-toggle-block' + | 'consent-allow' + | 'consent-block'; + +interface CategoryPayload { + onsite: { + status: boolean; + lbi: boolean; + source: string; + fow: string; + }; +} + +type ConsentType = 'behaviouralAds' | 'demographicAds' | 'programmaticAds'; +type ConsentData = Record<ConsentType, CategoryPayload>; + +interface ConsentPayload { + formOfWordsId: string; + consentSource: string; + data: ConsentData; +} + +type OnSaveCallback = ( + err: null | Error, + data: { consent: boolean; payload: ConsentPayload } +) => void; + +export interface SendConsentProps { + consentApiUrl: string; + onConsentSavedCallbacks: OnSaveCallback[]; + consentSource: string; + cookieDomain: string; + fow: FoWConfig; +} + +export interface _Response { + ok: boolean; + status?: number; +} + +export interface Actions { + onConsentChange: () => void; + sendConsent: (payload: SendConsentProps) => Promise<{ _response: _Response }>; +} + +export interface FoWConfig { + id: string; + version: string; +} + +export interface ButtonLabel { + label: string; + text?: string; +} + +export interface ButtonText { + allow: ButtonLabel; + block: ButtonLabel; + submit: ButtonLabel; +} + +export interface PrivacyManagerProps { + referrer?: string; + consent?: boolean; + cookieDomain?: string; + consentProxyApiHost: string; + userId: string; + legislationId: string; + fow: FoWConfig; + buttonText?: ButtonText; + consentSource: string; + onConsentSavedCallbacks?: OnSaveCallback[]; +} + +export interface BasePrivacyManagerProps extends PrivacyManagerProps { + actions: Actions; + isLoading?: boolean; + _response: _Response; +} + +export interface FormProps { + consent: boolean; + consentApiUrl: string; + sendConsent: Actions['sendConsent']; + trackingKeys: Record<TrackingKey, string>; + buttonText: ButtonText; + children: JSX.Element; +} + +export type PrivacyManager = (props: PrivacyManagerProps) => JSX.Element; From 1effbeecb1f779250a8bb2a0bc11db8df1fd6539 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Mon, 23 Nov 2020 17:53:44 +0000 Subject: [PATCH 635/760] Fix TS exports & improve definition readability --- components/x-privacy-manager/jsconfig.json | 4 - components/x-privacy-manager/tsconfig.json | 13 +++ components/x-privacy-manager/types.d.ts | 112 ++++++++++----------- 3 files changed, 66 insertions(+), 63 deletions(-) delete mode 100644 components/x-privacy-manager/jsconfig.json create mode 100644 components/x-privacy-manager/tsconfig.json diff --git a/components/x-privacy-manager/jsconfig.json b/components/x-privacy-manager/jsconfig.json deleted file mode 100644 index 75d668cf9..000000000 --- a/components/x-privacy-manager/jsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "include": ["src/**/*.js", "src/**/*.jsx", "./types.d.ts"], - "exclude": ["node_modules"] -} diff --git a/components/x-privacy-manager/tsconfig.json b/components/x-privacy-manager/tsconfig.json new file mode 100644 index 000000000..35200e4b6 --- /dev/null +++ b/components/x-privacy-manager/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "noEmit": true, + "allowJs": true, + "target": "es2015", + "module": "commonjs", + "strict": true, + "noImplicitAny": true, + "jsx": "react" + }, + "include": ["src/**/*.js", "src/**/*.jsx", "./types.d.ts"], + "exclude": ["node_modules"] +} diff --git a/components/x-privacy-manager/types.d.ts b/components/x-privacy-manager/types.d.ts index f1a9e1933..a1e959ded 100644 --- a/components/x-privacy-manager/types.d.ts +++ b/components/x-privacy-manager/types.d.ts @@ -1,100 +1,94 @@ +import * as React from 'react' + declare module '*.scss' { - const content: { [className: string]: string }; - export default content; + export const content: { [className: string]: string } } -type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string>; -type ConsentProxyEndpoints = Partial<{ [key in keyof ConsentProxyEndpoint]: string }>; +type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string> +type ConsentProxyEndpoints = Partial<{ [key in keyof ConsentProxyEndpoint]: string }> -type TrackingKey = - | 'advertising-toggle-allow' - | 'advertising-toggle-block' - | 'consent-allow' - | 'consent-block'; +type TrackingKey = 'advertising-toggle-allow' | 'advertising-toggle-block' | 'consent-allow' | 'consent-block' interface CategoryPayload { onsite: { - status: boolean; - lbi: boolean; - source: string; - fow: string; - }; + status: boolean + lbi: boolean + source: string + fow: string + } } -type ConsentType = 'behaviouralAds' | 'demographicAds' | 'programmaticAds'; -type ConsentData = Record<ConsentType, CategoryPayload>; +type ConsentType = 'behaviouralAds' | 'demographicAds' | 'programmaticAds' +type ConsentData = Record<ConsentType, CategoryPayload> interface ConsentPayload { - formOfWordsId: string; - consentSource: string; - data: ConsentData; + formOfWordsId: string + consentSource: string + data: ConsentData } -type OnSaveCallback = ( - err: null | Error, - data: { consent: boolean; payload: ConsentPayload } -) => void; +type OnSaveCallback = (err: null | Error, data: { consent: boolean; payload: ConsentPayload }) => void export interface SendConsentProps { - consentApiUrl: string; - onConsentSavedCallbacks: OnSaveCallback[]; - consentSource: string; - cookieDomain: string; - fow: FoWConfig; + consentApiUrl: string + onConsentSavedCallbacks: OnSaveCallback[] + consentSource: string + cookieDomain: string + fow: FoWConfig } export interface _Response { - ok: boolean; - status?: number; + ok: boolean + status?: number } export interface Actions { - onConsentChange: () => void; - sendConsent: (payload: SendConsentProps) => Promise<{ _response: _Response }>; + onConsentChange: () => void + sendConsent: (payload: SendConsentProps) => Promise<{ _response: _Response }> } export interface FoWConfig { - id: string; - version: string; + id: string + version: string } export interface ButtonLabel { - label: string; - text?: string; + label: string + text?: string } export interface ButtonText { - allow: ButtonLabel; - block: ButtonLabel; - submit: ButtonLabel; + allow: ButtonLabel + block: ButtonLabel + submit: ButtonLabel } export interface PrivacyManagerProps { - referrer?: string; - consent?: boolean; - cookieDomain?: string; - consentProxyApiHost: string; - userId: string; - legislationId: string; - fow: FoWConfig; - buttonText?: ButtonText; - consentSource: string; - onConsentSavedCallbacks?: OnSaveCallback[]; + referrer?: string + consent?: boolean + cookieDomain?: string + buttonText?: ButtonText + userId: string + legislationId: string + fow: FoWConfig + consentSource: string + consentProxyApiHost: string + onConsentSavedCallbacks?: OnSaveCallback[] } export interface BasePrivacyManagerProps extends PrivacyManagerProps { - actions: Actions; - isLoading?: boolean; - _response: _Response; + actions: Actions + isLoading?: boolean + _response: _Response } export interface FormProps { - consent: boolean; - consentApiUrl: string; - sendConsent: Actions['sendConsent']; - trackingKeys: Record<TrackingKey, string>; - buttonText: ButtonText; - children: JSX.Element; + consent: boolean + consentApiUrl: string + sendConsent: Actions['sendConsent'] + trackingKeys: Record<TrackingKey, string> + buttonText: ButtonText + children: React.ReactElement } -export type PrivacyManager = (props: PrivacyManagerProps) => JSX.Element; +export { PrivacyManager } from './src/privacy-manager' From bafbb37b0a4daecc5cfd2d7dd7b15b469524e8b7 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Tue, 24 Nov 2020 15:23:23 +0000 Subject: [PATCH 636/760] Test that data-trackable attrs are customisable --- .../src/__tests__/config.test.jsx | 61 +++++++++++-------- .../src/__tests__/helpers.js | 40 ++++++------ .../x-privacy-manager/src/components/form.jsx | 30 ++++----- .../src/components/radio-btn.jsx | 2 +- components/x-privacy-manager/types.d.ts | 12 ++-- 5 files changed, 77 insertions(+), 68 deletions(-) diff --git a/components/x-privacy-manager/src/__tests__/config.test.jsx b/components/x-privacy-manager/src/__tests__/config.test.jsx index a095fb9a1..8c42f3079 100644 --- a/components/x-privacy-manager/src/__tests__/config.test.jsx +++ b/components/x-privacy-manager/src/__tests__/config.test.jsx @@ -1,37 +1,48 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -import { defaultProps } from './helpers'; +import { defaultProps } from './helpers' -import { BasePrivacyManager } from '../privacy-manager'; +import { BasePrivacyManager } from '../privacy-manager' describe('Config', () => { it('renders the default UI', () => { - const subject = mount(<BasePrivacyManager {...defaultProps} />); - const labelTrue = subject.find('label[htmlFor="consent-true"]'); - const labelFalse = subject.find('label[htmlFor="consent-false"]'); + const subject = mount(<BasePrivacyManager {...defaultProps} />) + const labelTrue = subject.find('label[htmlFor="consent-true"]') + const labelFalse = subject.find('label[htmlFor="consent-false"]') - expect(labelTrue.text()).toBe('Allow' + 'See personalised adverts'); - expect(labelFalse.text()).toBe('Block' + 'Opt out of personalised adverts'); - }); + expect(labelTrue.text()).toBe('Allow' + 'See personalised adverts') + expect(labelFalse.text()).toBe('Block' + 'Opt out of personalised adverts') + }) it('renders custom Button text', () => { const buttonText = { allow: { label: 'Custom label', - text: 'Custom allow text', + text: 'Custom allow text' }, - submit: { label: 'Custom save' }, - }; - const props = { ...defaultProps, buttonText }; - - const subject = mount(<BasePrivacyManager {...props} />); - const labelTrue = subject.find('label[htmlFor="consent-true"]'); - const labelFalse = subject.find('label[htmlFor="consent-false"]'); - const labelSave = subject.find('button[type="submit"]'); - - expect(labelTrue.text()).toBe('Custom label' + 'Custom allow text'); - expect(labelFalse.text()).toBe('Block' + 'Opt out of personalised adverts'); - expect(labelSave.text()).toBe('Custom save'); - }); -}); + submit: { label: 'Custom save' } + } + const props = { ...defaultProps, buttonText } + + const subject = mount(<BasePrivacyManager {...props} />) + const labelTrue = subject.find('[data-trackable="ccpa-advertising-toggle-allow"] + label') + const labelFalse = subject.find('[data-trackable="ccpa-advertising-toggle-block"] + label') + const btnSave = subject.find('[data-trackable="ccpa-consent-block"]') + + expect(labelTrue.text()).toBe('Custom label' + 'Custom allow text') + expect(labelFalse.text()).toBe('Block' + 'Opt out of personalised adverts') + expect(btnSave.text()).toBe('Custom save') + }) + + it('renders legislation-specific data-trackable attrs', () => { + const props = { ...defaultProps, legislationId: 'gdpr' } + const subject = mount(<BasePrivacyManager {...props} />) + + const inputTrue = subject.find('[data-trackable="gdpr-advertising-toggle-allow"] + label') + const inputFalse = subject.find('[data-trackable="gdpr-advertising-toggle-block"] + label') + + expect(inputTrue.text()).toBe('Allow' + 'See personalised adverts') + expect(inputFalse.text()).toBe('Block' + 'Opt out of personalised adverts') + }) +}) diff --git a/components/x-privacy-manager/src/__tests__/helpers.js b/components/x-privacy-manager/src/__tests__/helpers.js index 76378aa96..d164f13ae 100644 --- a/components/x-privacy-manager/src/__tests__/helpers.js +++ b/components/x-privacy-manager/src/__tests__/helpers.js @@ -1,6 +1,5 @@ -export const CONSENT_PROXY_HOST = 'https://consent.ft.com'; -export const CONSENT_PROXY_ENDPOINT = - 'https://consent.ft.com/__consent/consent-record/FTPINK/abcde'; +export const CONSENT_PROXY_HOST = 'https://consent.ft.com' +export const CONSENT_PROXY_ENDPOINT = 'https://consent.ft.com/__consent/consent-record/FTPINK/abcde' export const buildPayload = (consent) => ({ consentSource: 'consuming-app', @@ -10,52 +9,53 @@ export const buildPayload = (consent) => ({ fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', lbi: true, source: 'consuming-app', - status: consent, - }, + status: consent + } }, demographicAds: { onsite: { fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', lbi: true, source: 'consuming-app', - status: consent, - }, + status: consent + } }, programmaticAds: { onsite: { fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', lbi: true, source: 'consuming-app', - status: consent, - }, - }, + status: consent + } + } }, cookieDomain: '.ft.com', - formOfWordsId: 'privacyCCPA', -}); + formOfWordsId: 'privacyCCPA' +}) export function checkPayload(opts, expected) { - const consents = JSON.parse(String(opts.body)).data; + const consents = JSON.parse(String(opts.body)).data - let worked = true; + let worked = true for (const category in consents) { - worked = worked && consents[category].onsite.status === expected; + worked = worked && consents[category].onsite.status === expected } - return worked; + return worked } export const defaultProps = { userId: 'abcde', + legislationId: 'ccpa', consentProxyApiHost: CONSENT_PROXY_HOST, consentSource: 'consuming-app', referrer: 'www.ft.com', cookieDomain: '.ft.com', fow: { id: 'privacyCCPA', - version: 'H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', + version: 'H0IeyQBalorD.6nTqqzhNTKECSgOPJCG' }, actions: { onConsentChange: jest.fn(() => {}), - sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }), - }, -}; + sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }) + } +} diff --git a/components/x-privacy-manager/src/components/form.jsx b/components/x-privacy-manager/src/components/form.jsx index a75481946..ea07ec8f4 100644 --- a/components/x-privacy-manager/src/components/form.jsx +++ b/components/x-privacy-manager/src/components/form.jsx @@ -1,33 +1,27 @@ -import { h } from '@financial-times/x-engine'; -import s from '../privacy-manager.scss'; +import { h } from '@financial-times/x-engine' +import s from '../privacy-manager.scss' /** * @param {import("../../types").FormProps} args */ -export const Form = ({ - consent, - consentApiUrl, - sendConsent, - trackingKeys, - buttonText, - children, -}) => { +export const Form = ({ consent, consentApiUrl, sendConsent, trackingKeys, buttonText, children }) => { /** @type {import('../../types').TrackingKey} */ - const consentAction = consent ? 'consent-allow' : 'consent-block'; - const btnTrackingId = trackingKeys[consentAction]; + const consentAction = consent ? 'consent-allow' : 'consent-block' + const btnTrackingId = trackingKeys[consentAction] + const isDisabled = typeof consent === 'undefined' /** @param {React.FormEvent} event */ const onSubmit = (event) => { - event && event.preventDefault(); - return sendConsent(); - }; + event && event.preventDefault() + return sendConsent() + } return ( <form action={consentApiUrl} onSubmit={onSubmit}> <div className={s.form__controls}>{children}</div> - <button className={s.form__submit} type="submit" data-trackable={btnTrackingId}> + <button className={s.form__submit} type="submit" data-trackable={btnTrackingId} disabled={isDisabled}> {buttonText.submit.label} </button> </form> - ); -}; + ) +} diff --git a/components/x-privacy-manager/src/components/radio-btn.jsx b/components/x-privacy-manager/src/components/radio-btn.jsx index 3e709402f..ad89d2248 100644 --- a/components/x-privacy-manager/src/components/radio-btn.jsx +++ b/components/x-privacy-manager/src/components/radio-btn.jsx @@ -17,7 +17,7 @@ import s from './radio-btn.scss' export function RadioBtn({ name, type, checked, trackingKeys, buttonText, onChange }) { const value = type === 'allow' const id = `${name}-${value}` - const trackingId = trackingKeys[`advertising-toggle-${value}`] + const trackingId = trackingKeys[`advertising-toggle-${type}`] return ( <div className={s.control}> diff --git a/components/x-privacy-manager/types.d.ts b/components/x-privacy-manager/types.d.ts index a1e959ded..2ccfe4626 100644 --- a/components/x-privacy-manager/types.d.ts +++ b/components/x-privacy-manager/types.d.ts @@ -4,12 +4,16 @@ declare module '*.scss' { export const content: { [className: string]: string } } -type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string> -type ConsentProxyEndpoints = Partial<{ [key in keyof ConsentProxyEndpoint]: string }> +export type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string> +export type ConsentProxyEndpoints = Partial<{ [key in keyof ConsentProxyEndpoint]: string }> -type TrackingKey = 'advertising-toggle-allow' | 'advertising-toggle-block' | 'consent-allow' | 'consent-block' +export type TrackingKey = + | 'advertising-toggle-allow' + | 'advertising-toggle-block' + | 'consent-allow' + | 'consent-block' -interface CategoryPayload { +export interface CategoryPayload { onsite: { status: boolean lbi: boolean From 20f6fdad8450dd2da60efd76586a9bd5c5f72976 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Tue, 24 Nov 2020 17:05:08 +0000 Subject: [PATCH 637/760] Distinguish between internal and exported type definitions --- components/x-privacy-manager/package.json | 2 +- components/x-privacy-manager/tsconfig.json | 2 +- components/x-privacy-manager/typings/internal.d.ts | 4 ++++ .../{types.d.ts => typings/x-privacy-manager.d.ts} | 6 +----- 4 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 components/x-privacy-manager/typings/internal.d.ts rename components/x-privacy-manager/{types.d.ts => typings/x-privacy-manager.d.ts} (93%) diff --git a/components/x-privacy-manager/package.json b/components/x-privacy-manager/package.json index c1b46f0cb..d08d35369 100644 --- a/components/x-privacy-manager/package.json +++ b/components/x-privacy-manager/package.json @@ -11,7 +11,7 @@ "module": "dist/privacy-manager.esm.js", "browser": "dist/privacy-manager.es5.js", "style": "dist/privacy-manager.css", - "types": "types.d.ts", + "types": "typings/x-privacy-manager.d.ts", "repository": { "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" diff --git a/components/x-privacy-manager/tsconfig.json b/components/x-privacy-manager/tsconfig.json index 35200e4b6..713037be8 100644 --- a/components/x-privacy-manager/tsconfig.json +++ b/components/x-privacy-manager/tsconfig.json @@ -8,6 +8,6 @@ "noImplicitAny": true, "jsx": "react" }, - "include": ["src/**/*.js", "src/**/*.jsx", "./types.d.ts"], + "include": ["src/**/*.js", "src/**/*.jsx", "typings/x-privacy-manager.d.ts"], "exclude": ["node_modules"] } diff --git a/components/x-privacy-manager/typings/internal.d.ts b/components/x-privacy-manager/typings/internal.d.ts new file mode 100644 index 000000000..c72e8a203 --- /dev/null +++ b/components/x-privacy-manager/typings/internal.d.ts @@ -0,0 +1,4 @@ +declare module '*.scss' { + const content: { [className: string]: string } + export default content +} diff --git a/components/x-privacy-manager/types.d.ts b/components/x-privacy-manager/typings/x-privacy-manager.d.ts similarity index 93% rename from components/x-privacy-manager/types.d.ts rename to components/x-privacy-manager/typings/x-privacy-manager.d.ts index 2ccfe4626..abc078f0b 100644 --- a/components/x-privacy-manager/types.d.ts +++ b/components/x-privacy-manager/typings/x-privacy-manager.d.ts @@ -1,9 +1,5 @@ import * as React from 'react' -declare module '*.scss' { - export const content: { [className: string]: string } -} - export type ConsentProxyEndpoint = Record<'core' | 'enhanced' | 'createOrUpdateRecord', string> export type ConsentProxyEndpoints = Partial<{ [key in keyof ConsentProxyEndpoint]: string }> @@ -95,4 +91,4 @@ export interface FormProps { children: React.ReactElement } -export { PrivacyManager } from './src/privacy-manager' +export { PrivacyManager } from '../src/privacy-manager' From be78556de3f0913bd6321b85a63d51bf3053f312 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Fri, 27 Nov 2020 11:26:10 +0000 Subject: [PATCH 638/760] Features: - Added customisable loginUrl - Removed width definitions to allow consuming apps handle layout - Refactored stories for clearer identification of roles - Updated references to types --- .../x-privacy-manager/src/components/form.jsx | 4 ++-- .../src/components/messages.jsx | 10 ++++++---- .../src/components/radio-btn.jsx | 5 ++--- .../x-privacy-manager/src/privacy-manager.jsx | 15 +++++++++------ .../src/privacy-manager.scss | 5 ----- components/x-privacy-manager/src/utils.js | 6 ++++-- .../x-privacy-manager/storybook/data.js | 2 ++ .../x-privacy-manager/storybook/index.js | 19 +++++-------------- .../storybook/stories/index.js | 17 +++++++++++++++++ .../legislation-ccpa.js} | 2 +- .../legislation-gdpr.js} | 2 +- .../x-privacy-manager/storybook/wrapper.js | 13 +++++++++++++ components/x-privacy-manager/tsconfig.json | 4 ++-- .../typings/x-privacy-manager.d.ts | 5 ++++- 14 files changed, 68 insertions(+), 41 deletions(-) create mode 100644 components/x-privacy-manager/storybook/stories/index.js rename components/x-privacy-manager/storybook/{story-legislation-ccpa.js => stories/legislation-ccpa.js} (85%) rename components/x-privacy-manager/storybook/{story-legislation-gdpr.js => stories/legislation-gdpr.js} (91%) create mode 100644 components/x-privacy-manager/storybook/wrapper.js diff --git a/components/x-privacy-manager/src/components/form.jsx b/components/x-privacy-manager/src/components/form.jsx index ea07ec8f4..bb9513448 100644 --- a/components/x-privacy-manager/src/components/form.jsx +++ b/components/x-privacy-manager/src/components/form.jsx @@ -2,10 +2,10 @@ import { h } from '@financial-times/x-engine' import s from '../privacy-manager.scss' /** - * @param {import("../../types").FormProps} args + * @param {import('../../typings/x-privacy-manager').FormProps} args */ export const Form = ({ consent, consentApiUrl, sendConsent, trackingKeys, buttonText, children }) => { - /** @type {import('../../types').TrackingKey} */ + /** @type {import('../../typings/x-privacy-manager').TrackingKey} */ const consentAction = consent ? 'consent-allow' : 'consent-block' const btnTrackingId = trackingKeys[consentAction] const isDisabled = typeof consent === 'undefined' diff --git a/components/x-privacy-manager/src/components/messages.jsx b/components/x-privacy-manager/src/components/messages.jsx index 4937cc7af..ff72b8fce 100644 --- a/components/x-privacy-manager/src/components/messages.jsx +++ b/components/x-privacy-manager/src/components/messages.jsx @@ -82,7 +82,7 @@ export function LoadingMessage() { /** * @param {boolean} isLoading - * @param {_Response} response + * @param {import('../../typings/x-privacy-manager')._Response} response * @param {string} referrer */ export function renderMessage(isLoading, response, referrer) { @@ -95,13 +95,15 @@ export function renderMessage(isLoading, response, referrer) { * Display a warning to users * @param {string} [userId] */ -export function renderLoggedOutWarning(userId) { +export function renderLoggedOutWarning(userId, loginUrl) { if (userId) return null + const cta = loginUrl ? <a href={loginUrl}>sign into your account</a> : 'sign into your account' + return ( <p className={`${s.consent__copy} ${s['consent__copy--cta']}`} data-component="login-cta"> - Please sign into your account before submitting your preferences to ensure these changes are applied - across all of your devices + Please {cta} before submitting your preferences to ensure these changes are applied across all of your + devices </p> ) } diff --git a/components/x-privacy-manager/src/components/radio-btn.jsx b/components/x-privacy-manager/src/components/radio-btn.jsx index ad89d2248..1c5a6627a 100644 --- a/components/x-privacy-manager/src/components/radio-btn.jsx +++ b/components/x-privacy-manager/src/components/radio-btn.jsx @@ -7,9 +7,8 @@ import s from './radio-btn.scss' * name: string, * type: "allow" | "block", * checked: boolean, - * trackingKeys: Record<string, string>, - * onChange: () => void, - * children?: Element + * trackingKeys: import('../../typings/x-privacy-manager').TrackingKeys, + * onChange: (value: boolean) => void, * }} args * * @returns {JSX.Element} diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx index 021327ee1..e9617c0ad 100644 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -14,7 +14,7 @@ const defaultButtonText = { } /** - * @param {import('../types').BasePrivacyManagerProps} Props + * @param {import('../typings/x-privacy-manager').BasePrivacyManagerProps} Props */ export function BasePrivacyManager({ userId, @@ -25,11 +25,11 @@ export function BasePrivacyManager({ consent, consentSource, consentProxyApiHost, - buttonText = {}, onConsentSavedCallbacks = [], + buttonText = {}, + loginUrl, actions, isLoading, - children, _response = undefined }) { // Shallowly merge supplied button labels with defaults @@ -44,6 +44,10 @@ export function BasePrivacyManager({ const trackingKeys = utils.getTrackingKeys(legislationId) const { sendConsent, onConsentChange } = actions + /** + * @param {"allow"|"block"} type + * @param {boolean} checked + */ const radioBtnProps = (type, checked) => ({ name: 'consent', type, @@ -53,7 +57,7 @@ export function BasePrivacyManager({ onChange: onConsentChange }) - /** @type {import('../types').FormProps} */ + /** @type {import('../typings/x-privacy-manager').FormProps} */ const formProps = { consent, consentApiUrl, @@ -72,8 +76,7 @@ export function BasePrivacyManager({ return ( <div className={s.consent} data-component="x-privacy-manager"> - {children} - {renderLoggedOutWarning(userId)} + {renderLoggedOutWarning(userId, loginUrl)} <div className={s.messages} aria-live="polite"> {renderMessage(isLoading, _response, referrer)} </div> diff --git a/components/x-privacy-manager/src/privacy-manager.scss b/components/x-privacy-manager/src/privacy-manager.scss index 2b1e63396..7b2f756f0 100644 --- a/components/x-privacy-manager/src/privacy-manager.scss +++ b/components/x-privacy-manager/src/privacy-manager.scss @@ -21,9 +21,6 @@ .consent { @include oTypographySans($scale: 0, $line-height: 1.6); - margin: auto; - max-width: map-get($o-grid-layouts, M); - & * { box-sizing: border-box; } @@ -34,8 +31,6 @@ } .consent__copy { - margin-top: oSpacingByName(s8); - & a { @include oTypographyLink; } diff --git a/components/x-privacy-manager/src/utils.js b/components/x-privacy-manager/src/utils.js index 3396364d1..93a4c371c 100644 --- a/components/x-privacy-manager/src/utils.js +++ b/components/x-privacy-manager/src/utils.js @@ -11,7 +11,8 @@ const trackingKeys = [ * e.g. { 'advertising-toggle-block': 'gdpr-advertising-toggle-block' } * * @param {string} legislationId - * @returns {Record<import("../types").TrackingKey, string>} + * + * @returns {import("@financial-times/x-privacy-manager").TrackingKeys} */ export function getTrackingKeys(legislationId) { /** @type Record<TrackingKey, string> */ @@ -30,7 +31,8 @@ export function getTrackingKeys(legislationId) { * cookiesOnly?: boolean; * cookieDomain?: string; * }} param - * @returns {import("../types").ConsentProxyEndpoint} + * + * @returns {import("@financial-times/x-privacy-manager").ConsentProxyEndpoint} */ export function getConsentProxyEndpoints({ userId, diff --git a/components/x-privacy-manager/storybook/data.js b/components/x-privacy-manager/storybook/data.js index 12a96e1d7..f4dcf4313 100644 --- a/components/x-privacy-manager/storybook/data.js +++ b/components/x-privacy-manager/storybook/data.js @@ -24,10 +24,12 @@ const defaultArgs = { legislationId: 'ccpa', consent: true, referrer: 'ft.com', + loginUrl: 'https://www.ft.com/login?location=/', fow: { id: 'privacyCCPA', version: 'H0IeyQBalorD.6nTqqzhNTKECSgOPJCG' }, + consent: true, consentSource: 'next-control-centre', consentProxyApiHost: CONSENT_API, buttonText: { diff --git a/components/x-privacy-manager/storybook/index.js b/components/x-privacy-manager/storybook/index.js index 25bc007e3..8b205ea70 100644 --- a/components/x-privacy-manager/storybook/index.js +++ b/components/x-privacy-manager/storybook/index.js @@ -1,22 +1,13 @@ -const { PrivacyManager } = require('../src/privacy-manager') +export { Wrapper as component } from './wrapper' -exports.component = PrivacyManager +export { default as package } from '../package.json' -exports.package = require('../package.json') - -exports.dependencies = { +export const dependencies = { 'o-loading': '^4.0.0', 'o-message': '^4.0.0', 'o-typography': '^6.0.0' } -exports.stories = [ - require('./story-legislation-ccpa'), - require('./story-legislation-gdpr'), - require('./story-consent-indeterminate'), - require('./story-consent-accepted'), - require('./story-consent-blocked'), - require('./story-save-failed') -] +export { stories } from './stories' -exports.knobs = require('./knobs') +export { default as knobs } from './knobs' diff --git a/components/x-privacy-manager/storybook/stories/index.js b/components/x-privacy-manager/storybook/stories/index.js new file mode 100644 index 000000000..a22920425 --- /dev/null +++ b/components/x-privacy-manager/storybook/stories/index.js @@ -0,0 +1,17 @@ +import { default as legislationCCPA } from './legislation-ccpa' +import { default as legislationGDPR } from './legislation-gdpr' + +import { default as consentIndeterminate } from './consent-indeterminate' +import { default as consentAccepted } from './consent-accepted' +import { default as consentBlocked } from './consent-blocked' + +import { default as saveFailed } from './save-failed' + +export const stories = [ + legislationCCPA, + legislationGDPR, + consentIndeterminate, + consentAccepted, + consentBlocked, + saveFailed +] diff --git a/components/x-privacy-manager/storybook/story-legislation-ccpa.js b/components/x-privacy-manager/storybook/stories/legislation-ccpa.js similarity index 85% rename from components/x-privacy-manager/storybook/story-legislation-ccpa.js rename to components/x-privacy-manager/storybook/stories/legislation-ccpa.js index b354b1d60..bddc5fdf4 100644 --- a/components/x-privacy-manager/storybook/story-legislation-ccpa.js +++ b/components/x-privacy-manager/storybook/stories/legislation-ccpa.js @@ -1,4 +1,4 @@ -const { defaults, getFetchMock } = require('./data') +const { defaults, getFetchMock } = require('../data') exports.title = 'Consent CCPA' diff --git a/components/x-privacy-manager/storybook/story-legislation-gdpr.js b/components/x-privacy-manager/storybook/stories/legislation-gdpr.js similarity index 91% rename from components/x-privacy-manager/storybook/story-legislation-gdpr.js rename to components/x-privacy-manager/storybook/stories/legislation-gdpr.js index 819be7079..fcd9e8fea 100644 --- a/components/x-privacy-manager/storybook/story-legislation-gdpr.js +++ b/components/x-privacy-manager/storybook/stories/legislation-gdpr.js @@ -1,4 +1,4 @@ -const { defaults, getFetchMock } = require('./data') +const { defaults, getFetchMock } = require('../data') exports.title = 'Consent GDPR' diff --git a/components/x-privacy-manager/storybook/wrapper.js b/components/x-privacy-manager/storybook/wrapper.js new file mode 100644 index 000000000..00340b579 --- /dev/null +++ b/components/x-privacy-manager/storybook/wrapper.js @@ -0,0 +1,13 @@ +import React from 'react' + +import { PrivacyManager } from '../src/privacy-manager' + +// Using a wrapper means that the component can avoid defining its own dimensions +// Layout is best handled by the consuming app +export function Wrapper(props) { + return ( + <div style={{ maxWidth: 740, margin: 'auto' }}> + <PrivacyManager {...props} /> + </div> + ) +} diff --git a/components/x-privacy-manager/tsconfig.json b/components/x-privacy-manager/tsconfig.json index 713037be8..ceec3d31c 100644 --- a/components/x-privacy-manager/tsconfig.json +++ b/components/x-privacy-manager/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "noEmit": true, + "outDir": "dist/types", "allowJs": true, "target": "es2015", "module": "commonjs", @@ -8,6 +8,6 @@ "noImplicitAny": true, "jsx": "react" }, - "include": ["src/**/*.js", "src/**/*.jsx", "typings/x-privacy-manager.d.ts"], + "include": ["src/**/*.js", "src/**/*.jsx", "typings/*.d.ts"], "exclude": ["node_modules"] } diff --git a/components/x-privacy-manager/typings/x-privacy-manager.d.ts b/components/x-privacy-manager/typings/x-privacy-manager.d.ts index abc078f0b..603cbf61c 100644 --- a/components/x-privacy-manager/typings/x-privacy-manager.d.ts +++ b/components/x-privacy-manager/typings/x-privacy-manager.d.ts @@ -9,6 +9,8 @@ export type TrackingKey = | 'consent-allow' | 'consent-block' +export type TrackingKeys = Record<TrackingKey, string> + export interface CategoryPayload { onsite: { status: boolean @@ -68,6 +70,7 @@ export interface PrivacyManagerProps { consent?: boolean cookieDomain?: string buttonText?: ButtonText + loginUrl?: string userId: string legislationId: string fow: FoWConfig @@ -86,7 +89,7 @@ export interface FormProps { consent: boolean consentApiUrl: string sendConsent: Actions['sendConsent'] - trackingKeys: Record<TrackingKey, string> + trackingKeys: TrackingKeys buttonText: ButtonText children: React.ReactElement } From 73c9379d98d0d50f5aa501a3e9d5115e3967fa3e Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Sat, 28 Nov 2020 12:21:54 +0000 Subject: [PATCH 639/760] Integrate changes from CPP-340-storybook-v6 --- .../x-privacy-manager/storybook/data.js | 30 ++++--- .../x-privacy-manager/storybook/index.js | 13 --- .../x-privacy-manager/storybook/index.jsx | 85 ++----------------- .../x-privacy-manager/storybook/knobs.js | 30 ------- .../storybook/stories/consent-accepted.js | 17 ++++ .../storybook/stories/consent-blocked.js | 14 +++ .../stories/consent-indeterminate.js | 17 ++++ .../storybook/stories/index.js | 17 ---- .../storybook/stories/legislation-ccpa.js | 20 ++--- .../storybook/stories/legislation-gdpr.js | 22 ++--- .../storybook/stories/save-failed.js | 14 +++ .../storybook/story-container.jsx | 31 +++++++ .../x-privacy-manager/storybook/wrapper.js | 13 --- package.json | 4 +- 14 files changed, 140 insertions(+), 187 deletions(-) delete mode 100644 components/x-privacy-manager/storybook/index.js delete mode 100644 components/x-privacy-manager/storybook/knobs.js create mode 100644 components/x-privacy-manager/storybook/stories/consent-accepted.js create mode 100644 components/x-privacy-manager/storybook/stories/consent-blocked.js create mode 100644 components/x-privacy-manager/storybook/stories/consent-indeterminate.js delete mode 100644 components/x-privacy-manager/storybook/stories/index.js create mode 100644 components/x-privacy-manager/storybook/stories/save-failed.js create mode 100644 components/x-privacy-manager/storybook/story-container.jsx delete mode 100644 components/x-privacy-manager/storybook/wrapper.js diff --git a/components/x-privacy-manager/storybook/data.js b/components/x-privacy-manager/storybook/data.js index f4dcf4313..b44eb92a5 100644 --- a/components/x-privacy-manager/storybook/data.js +++ b/components/x-privacy-manager/storybook/data.js @@ -1,6 +1,10 @@ -const CONSENT_API = 'https://mock-consent.ft.com' +import fetchMock from 'fetch-mock' -const referrers = { +export const CONSENT_API = 'https://mock-consent.ft.com' + +const legislations = ['gdpr', 'ccpa'] + +export const referrers = { 'ft.com': 'www.ft.com', 'exec-appointments.com': 'www.exec-appointments.com', 'fdibenchmark.com': 'www.fdibenchmark.com', @@ -19,10 +23,9 @@ const referrers = { Default: '' } -const defaultArgs = { +export const defaultArgs = { userId: 'fakeUserId', legislationId: 'ccpa', - consent: true, referrer: 'ft.com', loginUrl: 'https://www.ft.com/login?location=/', fow: { @@ -47,25 +50,24 @@ const defaultArgs = { } } -const defaultArgTypes = { +export const defaultArgTypes = { userId: { name: 'Authentication', control: { type: 'select', options: { loggedIn: defaultArgs.userId, loggedOut: undefined } } }, + legislationId: { control: { type: 'select', options: legislations } }, referrer: { control: { type: 'select', options: referrers } }, - consent: { control: { type: 'boolean' }, name: 'consent' } + consent: { control: { type: 'boolean' }, name: 'consent' }, + fow: { disable: true }, + consentSource: { disable: true }, + consentProxyApiHost: { disable: true }, + buttonText: { disable: true } } -const fetchMock = (status = 200, options = {}) => (fetchMock) => { +export const getFetchMock = (status = 200, options = {}) => { + fetchMock.reset() fetchMock.mock('https://mock-consent.ft.com/__consent/consent-record/FTPINK/fakeUserId', status, { delay: 1000, ...options }) } - -module.exports = { - CONSENT_API, - defaultArgs, - defaultArgTypes, - fetchMock -} diff --git a/components/x-privacy-manager/storybook/index.js b/components/x-privacy-manager/storybook/index.js deleted file mode 100644 index 8b205ea70..000000000 --- a/components/x-privacy-manager/storybook/index.js +++ /dev/null @@ -1,13 +0,0 @@ -export { Wrapper as component } from './wrapper' - -export { default as package } from '../package.json' - -export const dependencies = { - 'o-loading': '^4.0.0', - 'o-message': '^4.0.0', - 'o-typography': '^6.0.0' -} - -export { stories } from './stories' - -export { default as knobs } from './knobs' diff --git a/components/x-privacy-manager/storybook/index.jsx b/components/x-privacy-manager/storybook/index.jsx index bdb4c0f3b..e9002104b 100644 --- a/components/x-privacy-manager/storybook/index.jsx +++ b/components/x-privacy-manager/storybook/index.jsx @@ -1,79 +1,12 @@ -const { PrivacyManager } = require('../src/privacy-manager') -import React from 'react' -import { storiesOf } from '@storybook/react' -import { withKnobs } from '@storybook/addon-knobs' -import { Helmet } from 'react-helmet' -import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' -const pkg = require('../package.json') +export { LegislationCCPA } from './stories/legislation-ccpa' +export { LegislationGDPR } from './stories/legislation-gdpr' -const dependencies = { - 'o-loading': '^4.0.0', - 'o-message': '^4.0.0', - 'o-typography': '^6.0.0' -} +export { ConsentIndeterminate } from './stories/consent-indeterminate' +export { ConsentAccepted } from './stories/consent-accepted' +export { ConsentBlocked } from './stories/consent-blocked' -const knobs = require('./knobs') +export { SaveFailed } from './stories/save-failed' -storiesOf('x-privacy-manager', module) - .addDecorator(withKnobs) - .add('Consent: indeterminate', () => { - const { data, knobs: storyKnobs } = require('./story-consent-indeterminate') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} - <PrivacyManager {...props} /> - </div> - ) - }) - .add('Consent: accepted', () => { - const { data, knobs: storyKnobs } = require('./story-consent-accepted') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} - <PrivacyManager {...props} /> - </div> - ) - }) - .add('Consent: blocked', () => { - const { data, knobs: storyKnobs } = require('./story-consent-blocked') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} - <PrivacyManager {...props} /> - </div> - ) - }) - .add('Save failed', () => { - const { data, knobs: storyKnobs } = require('./story-save-failed') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} - <PrivacyManager {...props} /> - </div> - ) - }) +export default { + title: 'x-privacy-manager' +} diff --git a/components/x-privacy-manager/storybook/knobs.js b/components/x-privacy-manager/storybook/knobs.js deleted file mode 100644 index 618246527..000000000 --- a/components/x-privacy-manager/storybook/knobs.js +++ /dev/null @@ -1,30 +0,0 @@ -const referrers = { - 'ft.com': 'www.ft.com', - 'exec-appointments.com': 'www.exec-appointments.com', - 'fdibenchmark.com': 'www.fdibenchmark.com', - 'fdiintelligence.com': 'www.fdiintelligence.com', - 'fdimarkets.com': 'www.fdimarkets.com', - 'fdireports.com': 'www.fdireports.com', - 'ftadviser.com': 'www.ftadviser.com', - 'ftconfidentialresearch.com': 'www.ftconfidentialresearch.com', - 'globalriskregulator.com': 'www.globalriskregulator.com', - 'investorschronicle.co.uk': 'www.investorschronicle.co.uk', - 'non-execs.com': 'www.non-execs.com', - 'pensions-expert.com': 'www.pensions-expert.com', - 'pwmnet.com': 'www.pwmnet.com', - 'thebanker.com': 'www.thebanker.com', - 'thebankerdatabase.com': 'www.thebankerdatabase.com', - Undefined: '' -} - -module.exports = (data, { boolean, select }) => ({ - userId() { - return boolean('Authenticated', data.userId, undefined); - }, - consent() { - return boolean('Consent', data.consent, undefined) - }, - referrer() { - return select('Referrer', referrers, referrers['ft.com']) - } -}) diff --git a/components/x-privacy-manager/storybook/stories/consent-accepted.js b/components/x-privacy-manager/storybook/stories/consent-accepted.js new file mode 100644 index 000000000..292812e12 --- /dev/null +++ b/components/x-privacy-manager/storybook/stories/consent-accepted.js @@ -0,0 +1,17 @@ +import { StoryContainer } from '../story-container' +import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' + +/** + * @param {import('../../typings/x-privacy-manager').PrivacyManagerProps} args + */ +export const ConsentAccepted = (args) => { + getFetchMock(200) + return StoryContainer(args) +} + +ConsentAccepted.storyName = 'Consent: accepted' +ConsentAccepted.args = { + ...defaultArgs, + consent: true +} +ConsentAccepted.argTypes = defaultArgTypes diff --git a/components/x-privacy-manager/storybook/stories/consent-blocked.js b/components/x-privacy-manager/storybook/stories/consent-blocked.js new file mode 100644 index 000000000..91e12e67c --- /dev/null +++ b/components/x-privacy-manager/storybook/stories/consent-blocked.js @@ -0,0 +1,14 @@ +import { StoryContainer } from '../story-container' +import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' + +export const ConsentBlocked = (args) => { + getFetchMock(200) + return StoryContainer(args) +} + +ConsentBlocked.storyName = 'Consent: blocked' +ConsentBlocked.args = { + ...defaultArgs, + consent: false +} +ConsentBlocked.argTypes = defaultArgTypes diff --git a/components/x-privacy-manager/storybook/stories/consent-indeterminate.js b/components/x-privacy-manager/storybook/stories/consent-indeterminate.js new file mode 100644 index 000000000..cafef5540 --- /dev/null +++ b/components/x-privacy-manager/storybook/stories/consent-indeterminate.js @@ -0,0 +1,17 @@ +import { StoryContainer } from '../story-container' +import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' + +/** + * @param {import('../../typings/x-privacy-manager').PrivacyManagerProps} args + */ +export const ConsentIndeterminate = (args) => { + getFetchMock(200) + return StoryContainer(args) +} + +ConsentIndeterminate.storyName = 'Consent: indeterminate' +ConsentIndeterminate.args = { + ...defaultArgs, + consent: undefined +} +ConsentIndeterminate.argTypes = defaultArgTypes diff --git a/components/x-privacy-manager/storybook/stories/index.js b/components/x-privacy-manager/storybook/stories/index.js deleted file mode 100644 index a22920425..000000000 --- a/components/x-privacy-manager/storybook/stories/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import { default as legislationCCPA } from './legislation-ccpa' -import { default as legislationGDPR } from './legislation-gdpr' - -import { default as consentIndeterminate } from './consent-indeterminate' -import { default as consentAccepted } from './consent-accepted' -import { default as consentBlocked } from './consent-blocked' - -import { default as saveFailed } from './save-failed' - -export const stories = [ - legislationCCPA, - legislationGDPR, - consentIndeterminate, - consentAccepted, - consentBlocked, - saveFailed -] diff --git a/components/x-privacy-manager/storybook/stories/legislation-ccpa.js b/components/x-privacy-manager/storybook/stories/legislation-ccpa.js index bddc5fdf4..5c7f6e431 100644 --- a/components/x-privacy-manager/storybook/stories/legislation-ccpa.js +++ b/components/x-privacy-manager/storybook/stories/legislation-ccpa.js @@ -1,13 +1,11 @@ -const { defaults, getFetchMock } = require('../data') +import { StoryContainer } from '../story-container' +import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' -exports.title = 'Consent CCPA' +export const LegislationCCPA = (args) => { + getFetchMock(200) + return StoryContainer(args) +} -exports.data = { ...defaults, consent: true } - -exports.knobs = Object.keys(exports.data) - -exports.fetchMock = getFetchMock() - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module +LegislationCCPA.storyName = 'Legislation: CCPA' +LegislationCCPA.args = defaultArgs +LegislationCCPA.argTypes = defaultArgTypes diff --git a/components/x-privacy-manager/storybook/stories/legislation-gdpr.js b/components/x-privacy-manager/storybook/stories/legislation-gdpr.js index fcd9e8fea..20e523d54 100644 --- a/components/x-privacy-manager/storybook/stories/legislation-gdpr.js +++ b/components/x-privacy-manager/storybook/stories/legislation-gdpr.js @@ -1,9 +1,8 @@ -const { defaults, getFetchMock } = require('../data') +import { StoryContainer } from '../story-container' +import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' -exports.title = 'Consent GDPR' - -exports.data = { - ...defaults, +const args = { + ...defaultArgs, legislationId: 'gdpr', consent: undefined, buttonText: { @@ -18,10 +17,11 @@ exports.data = { } } -exports.knobs = Object.keys(exports.data) - -exports.fetchMock = getFetchMock() +export const LegislationGDPR = (args) => { + getFetchMock(200) + return StoryContainer(args) +} -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module +LegislationGDPR.storyName = 'Legislation: GDPR' +LegislationGDPR.args = args +LegislationGDPR.argTypes = defaultArgTypes diff --git a/components/x-privacy-manager/storybook/stories/save-failed.js b/components/x-privacy-manager/storybook/stories/save-failed.js new file mode 100644 index 000000000..ac1d88bf7 --- /dev/null +++ b/components/x-privacy-manager/storybook/stories/save-failed.js @@ -0,0 +1,14 @@ +import { StoryContainer } from '../story-container' +import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' + +/** + * @param {import('../../typings/x-privacy-manager').PrivacyManagerProps} args + */ +export const SaveFailed = (args) => { + getFetchMock(500) + return StoryContainer(args) +} + +SaveFailed.storyName = 'Save failed' +SaveFailed.args = defaultArgs +SaveFailed.argTypes = defaultArgTypes diff --git a/components/x-privacy-manager/storybook/story-container.jsx b/components/x-privacy-manager/storybook/story-container.jsx new file mode 100644 index 000000000..800c5fb00 --- /dev/null +++ b/components/x-privacy-manager/storybook/story-container.jsx @@ -0,0 +1,31 @@ +import React from 'react' +import { Helmet } from 'react-helmet' + +import BuildService from '../../../.storybook/build-service' +import { default as pkg } from '../package.json' +import { PrivacyManager } from '../src/privacy-manager' + +const dependencies = { + 'o-loading': '^4.0.0', + 'o-message': '^4.0.0', + 'o-typography': '^6.0.0' +} + +/** + * @param {import("../typings/x-privacy-manager").PrivacyManagerProps} args + */ +export function StoryContainer(args) { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + {pkg.style && ( + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> + </Helmet> + )} + <div style={{ maxWidth: 740, margin: 'auto' }}> + <PrivacyManager {...args} /> + </div> + </div> + ) +} diff --git a/components/x-privacy-manager/storybook/wrapper.js b/components/x-privacy-manager/storybook/wrapper.js deleted file mode 100644 index 00340b579..000000000 --- a/components/x-privacy-manager/storybook/wrapper.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' - -import { PrivacyManager } from '../src/privacy-manager' - -// Using a wrapper means that the component can avoid defining its own dimensions -// Layout is best handled by the consuming app -export function Wrapper(props) { - return ( - <div style={{ maxWidth: 740, margin: 'auto' }}> - <PrivacyManager {...props} /> - </div> - ) -} diff --git a/package.json b/package.json index 00a58d936..a4ffc2c78 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "devDependencies": { "@babel/core": "^7.4.5", "@financial-times/athloi": "^1.0.0-beta.26", - "@storybook/addon-essentials": "^6.0.28", - "@storybook/react": "^6.0.28", + "@storybook/addon-essentials": "^6.1.8", + "@storybook/react": "^6.1.8", "@types/jest": "26.0.0", "@typescript-eslint/parser": "^3.0.0", "babel-loader": "^8.0.4", From 4081b135f59d0e93e78b6f9e87d850532f322bb0 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Mon, 30 Nov 2020 14:24:24 +0000 Subject: [PATCH 640/760] Integrate Vasil's cookieDomain-related changes --- components/x-privacy-manager/readme.md | 1 + .../src/__tests__/helpers.js | 12 +- .../src/__tests__/state.test.jsx | 166 ++++++++++-------- components/x-privacy-manager/src/actions.js | 5 +- 4 files changed, 96 insertions(+), 88 deletions(-) diff --git a/components/x-privacy-manager/readme.md b/components/x-privacy-manager/readme.md index 18cc9b9d4..9cb4efb95 100644 --- a/components/x-privacy-manager/readme.md +++ b/components/x-privacy-manager/readme.md @@ -42,6 +42,7 @@ customCallback( payload: { formOfWordsId: string, consentSource: string, + cookieDomain?: string, data: { ['behaviouralAds' | 'demographicAds' | 'programmaticAds']: { onsite: { diff --git a/components/x-privacy-manager/src/__tests__/helpers.js b/components/x-privacy-manager/src/__tests__/helpers.js index d164f13ae..8ac2e4073 100644 --- a/components/x-privacy-manager/src/__tests__/helpers.js +++ b/components/x-privacy-manager/src/__tests__/helpers.js @@ -33,21 +33,11 @@ export const buildPayload = (consent) => ({ formOfWordsId: 'privacyCCPA' }) -export function checkPayload(opts, expected) { - const consents = JSON.parse(String(opts.body)).data - - let worked = true - for (const category in consents) { - worked = worked && consents[category].onsite.status === expected - } - return worked -} - export const defaultProps = { userId: 'abcde', legislationId: 'ccpa', - consentProxyApiHost: CONSENT_PROXY_HOST, consentSource: 'consuming-app', + consentProxyApiHost: CONSENT_PROXY_HOST, referrer: 'www.ft.com', cookieDomain: '.ft.com', fow: { diff --git a/components/x-privacy-manager/src/__tests__/state.test.jsx b/components/x-privacy-manager/src/__tests__/state.test.jsx index 12d788291..eac9093dc 100644 --- a/components/x-privacy-manager/src/__tests__/state.test.jsx +++ b/components/x-privacy-manager/src/__tests__/state.test.jsx @@ -1,88 +1,108 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); -const fetchMock = require('fetch-mock'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') +const fetchMock = require('fetch-mock') -import * as helpers from './helpers'; +import * as helpers from './helpers' -import { PrivacyManager } from '../privacy-manager'; +import { PrivacyManager } from '../privacy-manager' -const checkInput = ({ consent }) => () => { - const props = { ...helpers.defaultProps, consent }; - const subject = mount(<PrivacyManager {...props} />); - const input = subject.find(`input[value="${consent}"]`).first(); - - // Verify that initial props are correctly reflected - expect(input.prop('checked')).toBe(true); -}; +function getLastFetchPayload() { + return JSON.parse(fetchMock.lastOptions().body) +} describe('x-privacy-manager', () => { - describe('Messaging', () => { - beforeEach(() => { - fetchMock.reset(); - const okResponse = { - body: { a: 'b' }, - status: 200, - }; - - const targetUrl = helpers.CONSENT_PROXY_ENDPOINT - fetchMock.mock(targetUrl, okResponse, { delay: 500 }); - }); - - it('defaults to "undefined"', () => { - const subject = mount(<PrivacyManager {...helpers.defaultProps} />); - subject.find('input').forEach((input) => expect(input.prop('checked')).toBe(false)); - }); - - it('highlights explicitly set consent correctly: false', checkInput({ consent: false })); - it('highlights explicitly set consent correctly: true', checkInput({ consent: true })); - - it('handles a change of consent', async () => { - const callback1 = jest.fn(); - const callback2 = jest.fn(); - const consentVal = true; - + describe('handling consent choices', () => { + function setup(propOverrides = {}) { const props = { ...helpers.defaultProps, - consent: true, - onConsentSavedCallbacks: [callback1, callback2], - }; - const payload = helpers.buildPayload(consentVal); - - const subject = mount(<PrivacyManager {...props} />); - const form = subject.find('form').first(); - const inputTrue = subject.find('input[value="true"]').first(); - const inputFalse = subject.find('input[value="false"]').first(); - - // Switch consent to false and submit form - await inputFalse.prop('onChange')(undefined); - await form.prop('onSubmit')(undefined); + onConsentSavedCallbacks: [jest.fn(), jest.fn()], + ...propOverrides + } + const subject = mount(<PrivacyManager {...props} />) + + return { + subject, + callbacks: props.onConsentSavedCallbacks, + async submitConsent(value) { + // Switch consent to false and submit form + await subject.find(`input[value="${value}"]`).first().prop('onChange')(undefined) + await subject.find('form').first().prop('onSubmit')(undefined) + + // Reconcile snapshot with state + subject.update() + } + } + } - // Reconcile snapshot with state - await subject.update(); + beforeEach(() => { + fetchMock.reset() + fetchMock.config.overwriteRoutes = true + const okResponse = { + body: { a: 'b' }, + status: 200 + } + fetchMock.mock(helpers.CONSENT_PROXY_ENDPOINT, okResponse, { delay: 500 }) + }) - // Check that fetch was called with the correct values - expect(helpers.checkPayload(fetchMock.lastOptions(), false)).toBe(true); + it('handles consecutive changes of consent', async () => { + let payload + const { subject, callbacks, submitConsent } = setup({ consent: true }) + const optInInput = subject.find('[data-trackable="ccpa-advertising-toggle-allow"]').first() - // Switch consent back to true and resubmit form - await inputTrue.prop('onChange')(undefined); - await form.prop('onSubmit')(undefined); + await submitConsent(false) - // Reconcile snapshot with state - await subject.update(); + // Check fetch and both callbacks were run with correct `payload` values + payload = helpers.buildPayload(false) + expect(getLastFetchPayload()).toEqual(payload) + expect(callbacks[0]).toHaveBeenCalledWith(null, { payload, consent: false }) + expect(callbacks[1]).toHaveBeenCalledWith(null, { payload, consent: false }) - // Check both callbacks were run with `payload` - expect(callback1).toHaveBeenCalledWith(null, { payload, consent: true }); - expect(callback2).toHaveBeenCalledWith(null, { payload, consent: true }); + await submitConsent(true) - // Check that fetch was called with the correct values - expect(helpers.checkPayload(fetchMock.lastOptions(), true)).toBe(true); + // Check fetch and both callbacks were run with correct `payload` values + payload = helpers.buildPayload(true) + expect(getLastFetchPayload()).toEqual(payload) + expect(callbacks[0]).toHaveBeenCalledWith(null, { payload, consent: true }) + expect(callbacks[1]).toHaveBeenCalledWith(null, { payload, consent: true }) // Verify that confimatory nmessage is displayed - const message = subject.find('[data-o-component="o-message"]').first(); - const link = message.find('[data-component="referrer-link"]'); - // expect(message).toHaveClassName('o-message--success'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - expect(inputTrue).toHaveProp('checked', true); - }); - }); -}); + const message = subject.find('[data-o-component="o-message"]').first() + const link = message.find('[data-component="referrer-link"]') + expect(message).toHaveClassName('o-message--success') + expect(link).toHaveProp('href', 'https://www.ft.com/') + expect(optInInput).toHaveProp('checked', true) + }) + + it('when provided, passes the cookieDomain prop in the fetch and callback payload', async () => { + const { callbacks, submitConsent } = setup({ cookieDomain: '.ft.com' }) + const payload = { ...helpers.buildPayload(false), cookieDomain: '.ft.com' } + + await submitConsent(false) + + // Check fetch and both callbacks were run with correct `payload` values + expect(getLastFetchPayload()).toEqual(payload) + expect(callbacks[0]).toHaveBeenCalledWith(null, { payload, consent: false }) + expect(callbacks[1]).toHaveBeenCalledWith(null, { payload, consent: false }) + }) + + it('passes error object to callbacks when fetch fails', async () => { + const { callbacks, submitConsent } = setup() + const payload = helpers.buildPayload(false) + + // Override fetch-mock to fail requests + fetchMock.mock(helpers.CONSENT_PROXY_ENDPOINT, { status: 500 }, { delay: 500 }) + + await submitConsent(false) + + // calls fetch with the correct payload + expect(getLastFetchPayload()).toEqual(payload) + + // Calls both callbacks with an error as first argument + callbacks.forEach((callback) => { + const [errorArgument, resultArgument] = callback.mock.calls.pop() + expect(errorArgument).toBeInstanceOf(Error) + expect(resultArgument).toEqual({ payload, consent: false }) + }) + }) + }) +}) diff --git a/components/x-privacy-manager/src/actions.js b/components/x-privacy-manager/src/actions.js index 442688f79..8bdcd6f15 100644 --- a/components/x-privacy-manager/src/actions.js +++ b/components/x-privacy-manager/src/actions.js @@ -36,13 +36,10 @@ function sendConsent({ consentApiUrl, onConsentSavedCallbacks, consentSource, co } if (cookieDomain) { - // Optionally specify the domain for the cookie consent api will set + // Optionally specify the domain for the cookie to set on the Consent API payload.cookieDomain = cookieDomain } - // eslint-disable-next-line - console.log({ payload }) - try { const res = await fetch(consentApiUrl, { method: 'POST', From 077882a4fc8b6975cd46add14d88fd42f706ec70 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Thu, 3 Dec 2020 17:10:59 +0000 Subject: [PATCH 641/760] re-add cookiesOnly prop --- .../src/__tests__/utils.test.js | 40 +++++++++---------- .../x-privacy-manager/src/privacy-manager.jsx | 2 + .../typings/x-privacy-manager.d.ts | 1 + 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/components/x-privacy-manager/src/__tests__/utils.test.js b/components/x-privacy-manager/src/__tests__/utils.test.js index 519ca750a..306250c23 100644 --- a/components/x-privacy-manager/src/__tests__/utils.test.js +++ b/components/x-privacy-manager/src/__tests__/utils.test.js @@ -1,4 +1,4 @@ -const { getTrackingKeys, getConsentProxyEndpoints } = require('../utils'); +const { getTrackingKeys, getConsentProxyEndpoints } = require('../utils') describe('getTrackingKeys', () => { it('Creates legislation-specific tracking event names', () => { @@ -6,43 +6,43 @@ describe('getTrackingKeys', () => { 'advertising-toggle-block': 'ccpa-advertising-toggle-block', 'advertising-toggle-allow': 'ccpa-advertising-toggle-allow', 'consent-allow': 'ccpa-consent-allow', - 'consent-block': 'ccpa-consent-block', - }); - }); -}); + 'consent-block': 'ccpa-consent-block' + }) + }) +}) describe('getConsentProxyEndpoints', () => { const params = { userId: 'abcde', consentProxyApiHost: 'https://consent.ft.com', - cookieDomain: '.ft.com', - }; + cookieDomain: '.ft.com' + } - const defaultEndpoint = 'https://consent.ft.com/__consent/consent-record-cookie'; + const defaultEndpoint = 'https://consent.ft.com/__consent/consent-record-cookie' it('generates endpoints for logged-in users', () => { expect(getConsentProxyEndpoints(params)).toEqual({ core: `https://consent.ft.com/__consent/consent-record/FTPINK/abcde`, enhanced: `https://consent.ft.com/__consent/consent/FTPINK/abcde`, - createOrUpdateRecord: `https://consent.ft.com/__consent/consent-record/FTPINK/abcde`, - }); - }); + createOrUpdateRecord: `https://consent.ft.com/__consent/consent-record/FTPINK/abcde` + }) + }) it('generates endpoints for logged-out users', () => { - const loggedOutParams = { ...params, userId: undefined }; + const loggedOutParams = { ...params, userId: undefined } expect(getConsentProxyEndpoints(loggedOutParams)).toEqual({ core: defaultEndpoint, enhanced: defaultEndpoint, - createOrUpdateRecord: defaultEndpoint, - }); - }); + createOrUpdateRecord: defaultEndpoint + }) + }) it('generates endpoints for cookie-only circumstances', () => { - const loggedOutParams = { ...params, cookiesOnly: true }; + const loggedOutParams = { ...params, cookiesOnly: true } expect(getConsentProxyEndpoints(loggedOutParams)).toEqual({ core: defaultEndpoint, enhanced: defaultEndpoint, - createOrUpdateRecord: defaultEndpoint, - }); - }); -}); + createOrUpdateRecord: defaultEndpoint + }) + }) +}) diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx index e9617c0ad..964cdc0d7 100644 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -20,6 +20,7 @@ export function BasePrivacyManager({ userId, referrer, legislationId, + cookiesOnly, cookieDomain, fow, consent, @@ -38,6 +39,7 @@ export function BasePrivacyManager({ const consentProxyEndpoints = utils.getConsentProxyEndpoints({ userId, consentProxyApiHost, + cookiesOnly, cookieDomain }) const consentApiUrl = consentProxyEndpoints.createOrUpdateRecord diff --git a/components/x-privacy-manager/typings/x-privacy-manager.d.ts b/components/x-privacy-manager/typings/x-privacy-manager.d.ts index 603cbf61c..55939f16d 100644 --- a/components/x-privacy-manager/typings/x-privacy-manager.d.ts +++ b/components/x-privacy-manager/typings/x-privacy-manager.d.ts @@ -68,6 +68,7 @@ export interface ButtonText { export interface PrivacyManagerProps { referrer?: string consent?: boolean + cookiesOnly?: boolean cookieDomain?: string buttonText?: ButtonText loginUrl?: string From 77c7029f3f08f5c5876319eba91016cc2b158508 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Mon, 7 Dec 2020 12:27:59 +0000 Subject: [PATCH 642/760] Ensure that other components match master --- .storybook/storybook.utils.js | 37 --- components/x-gift-article/storybook/index.jsx | 195 +++++++-------- .../x-live-blog-post/storybook/index.jsx | 14 +- .../x-podcast-launchers/storybook/example.js | 16 -- .../x-podcast-launchers/storybook/index.jsx | 46 ++-- .../x-podcast-launchers/storybook/knobs.js | 5 - components/x-styling-demo/storybook/index.jsx | 37 --- .../x-styling-demo/storybook/styling.js | 12 - .../x-teaser-timeline/storybook/index.jsx | 40 ++- .../x-teaser-timeline/storybook/knobs.js | 19 -- components/x-teaser/storybook/index.jsx | 232 ++++++++---------- 11 files changed, 254 insertions(+), 399 deletions(-) delete mode 100644 .storybook/storybook.utils.js delete mode 100644 components/x-podcast-launchers/storybook/example.js delete mode 100644 components/x-podcast-launchers/storybook/knobs.js delete mode 100644 components/x-styling-demo/storybook/styling.js delete mode 100644 components/x-teaser-timeline/storybook/knobs.js diff --git a/.storybook/storybook.utils.js b/.storybook/storybook.utils.js deleted file mode 100644 index 855e6865e..000000000 --- a/.storybook/storybook.utils.js +++ /dev/null @@ -1,37 +0,0 @@ -import * as knobsAddon from '@storybook/addon-knobs' - -const defaultKnobs = () => ({}) - -/** - * Create Props - * @param {{ [key: string]: any }} defaultData - * @param {String[]} allowedKnobs - * @param {Function} hydrateKnobs - */ -function createProps(defaultData, allowedKnobs = [], hydrateKnobs = defaultKnobs) { - // Inject knobs add-on into given dependency container - const knobs = hydrateKnobs(defaultData, knobsAddon) - // Mix the available knob props into default data - const mixedProps = { ...defaultData, ...knobs } - - if (allowedKnobs.length === 0) { - return mixedProps - } - - return allowedKnobs.reduce((map, prop) => { - if (mixedProps.hasOwnProperty(prop)) { - const value = mixedProps[prop] - - // Knobs are functions which need calling to register them - if (typeof value === 'function') { - map[prop] = value() - } else { - map[prop] = value - } - } - - return map - }, {}) -} - -export default createProps diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index a11ed2249..83c786ae0 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -1,105 +1,108 @@ const { GiftArticle } = require('../dist/GiftArticle.cjs') +import fetchMock from 'fetch-mock' import React from 'react' -import { storiesOf } from '@storybook/react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' -const pkg = require('../package.json') const dependencies = { 'o-fonts': '^3.0.0' } -storiesOf('x-gift-article', module) - .addDecorator(withKnobs) - .add('With gift credits', () => { - const { data, knobs: storyKnobs } = require('./with-gift-credits') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('Without gift credits', () => { - const { data, knobs: storyKnobs } = require('./without-gift-credits') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('With gift link', () => { - const { data, knobs: storyKnobs } = require('./with-gift-link') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('Free article', () => { - const { data, knobs: storyKnobs } = require('./free-article') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('Native share', () => { - const { data, knobs: storyKnobs } = require('./native-share') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) - .add('Error response', () => { - const { data, knobs: storyKnobs } = require('./error-response') - const props = createProps(data, storyKnobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-gift-article/${pkg.style}`} /> - </Helmet> - )} - <GiftArticle {...props} /> - </div> - ) - }) +export default { + title: 'x-gift-article' +} + +export const WithGiftCredits = (args) => { + require('./with-gift-credits').fetchMock(fetchMock) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> + <GiftArticle {...args} /> + </div> + ) +} +WithGiftCredits.storyName = 'With gift credits' +WithGiftCredits.args = require('./with-gift-credits').args + +export const WithoutGiftCredits = (args) => { + require('./without-gift-credits').fetchMock(fetchMock) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> + <GiftArticle {...args} /> + </div> + ) +} + +WithoutGiftCredits.storyName = 'Without gift credits' +WithoutGiftCredits.args = require('./without-gift-credits').args + +export const WithGiftLink = (args) => { + require('./with-gift-link').fetchMock(fetchMock) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> + <GiftArticle {...args} /> + </div> + ) +} + +WithGiftLink.storyName = 'With gift link' +WithGiftLink.args = require('./with-gift-link').args + +export const FreeArticle = (args) => { + require('./free-article').fetchMock(fetchMock) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> + <GiftArticle {...args} /> + </div> + ) +} + +FreeArticle.storyName = 'Free article' +FreeArticle.args = require('./free-article').args + +export const NativeShare = (args) => { + require('./native-share').fetchMock(fetchMock) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> + <GiftArticle {...args} /> + </div> + ) +} + +NativeShare.storyName = 'Native share' +NativeShare.args = require('./native-share').args + +export const ErrorResponse = (args) => { + require('./error-response').fetchMock(fetchMock) + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Helmet> + <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> + </Helmet> + <GiftArticle {...args} /> + </div> + ) +} + +ErrorResponse.storyName = 'Error response' +ErrorResponse.args = require('./error-response').args diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index 2154f6e0e..2108130ee 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -1,8 +1,18 @@ import React from 'react' import { LiveBlogPost } from '../src/LiveBlogPost' -const defaultProps = { - id: '12345', +export default { + title: 'x-live-blog-post', + parameters: { + escapeHTML: false + } +} + +export const ContentBody = (args) => { + return <LiveBlogPost {...args} /> +} + +ContentBody.args = { title: 'Turkey’s virus deaths may be 25% higher than official figure', isBreakingNews: false, bodyHTML: diff --git a/components/x-podcast-launchers/storybook/example.js b/components/x-podcast-launchers/storybook/example.js deleted file mode 100644 index 6b4e91494..000000000 --- a/components/x-podcast-launchers/storybook/example.js +++ /dev/null @@ -1,16 +0,0 @@ -const { brand } = require('@financial-times/n-concept-ids') - -exports.title = 'Example' - -exports.data = { - conceptId: brand.rachmanReviewPodcast, - conceptName: 'Rachman Review', - isFollowed: false, - csrfToken: 'token', - acastRSSHost: 'https://access.acast.com', - acastAccessToken: 'abc-123' -} - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module diff --git a/components/x-podcast-launchers/storybook/index.jsx b/components/x-podcast-launchers/storybook/index.jsx index adf5d2f85..30d727727 100644 --- a/components/x-podcast-launchers/storybook/index.jsx +++ b/components/x-podcast-launchers/storybook/index.jsx @@ -1,11 +1,8 @@ const { PodcastLaunchers } = require('../dist/PodcastLaunchers.cjs') +const { brand } = require('@financial-times/n-concept-ids') import React from 'react' -import { storiesOf } from '@storybook/react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' -const pkg = require('../package.json') // Set up basic document styling using the Origami build service const dependencies = { @@ -15,22 +12,27 @@ const dependencies = { 'o-forms': '^7.0.0' } -const knobs = require('./knobs') +export default { + title: 'x-podcast-launchers' +} + +export const Example = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Helmet> + <link rel="stylesheet" href={`components/x-podcast-launchers/dist/PodcastLaunchers.css`} /> + </Helmet> + <PodcastLaunchers {...args} /> + </div> + ) +} -storiesOf('x-podcast-launchers', module) - .addDecorator(withKnobs) - .add('Example', () => { - const { data, knobs: storyKnobs } = require('./example') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-podcast-launchers/${pkg.style}`} /> - </Helmet> - )} - <PodcastLaunchers {...props} /> - </div> - ) - }) +Example.args = { + conceptId: brand.rachmanReviewPodcast, + conceptName: 'Rachman Review', + isFollowed: false, + csrfToken: 'token', + acastRSSHost: 'https://access.acast.com', + acastAccessToken: 'abc-123' +} diff --git a/components/x-podcast-launchers/storybook/knobs.js b/components/x-podcast-launchers/storybook/knobs.js deleted file mode 100644 index 4f9ada23e..000000000 --- a/components/x-podcast-launchers/storybook/knobs.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = (data, { text }) => ({ - conceptId: text('Concept id', data.conceptId), - acastRSSHost: text('Acast RSS host', data.acastRSSHost), - acastAccessToken: text('Acast Access token', data.acastAccessToken) -}) diff --git a/components/x-styling-demo/storybook/index.jsx b/components/x-styling-demo/storybook/index.jsx index bcb7125c8..40758b852 100644 --- a/components/x-styling-demo/storybook/index.jsx +++ b/components/x-styling-demo/storybook/index.jsx @@ -1,6 +1,5 @@ const { Button } = require('../dist/Button.cjs') import React from 'react' -<<<<<<< HEAD import { Helmet } from 'react-helmet' export default { @@ -18,39 +17,3 @@ export const Styling = (args) => { ) } Styling.args = { danger: false, large: false } -======= -import { storiesOf } from '@storybook/react' -import { withKnobs } from '@storybook/addon-knobs' -import { Helmet } from 'react-helmet' -import createProps from '../../../.storybook/storybook.utils' -const path = require('path') -const pkg = require('../package.json') -const name = path.basename(pkg.name) - -const knobs = (data, { boolean }) => ({ - danger() { - return boolean('Danger', data.danger) - }, - - large() { - return boolean('Large', data.large) - } -}) - -storiesOf('x-styling-demo', module) - .addDecorator(withKnobs) - .add('Styling', () => { - const { data, knobs: storyKnobs } = require('./styling') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Button {...props} /> - </div> - ) - }) ->>>>>>> dbbaa6f0 (converted buildStory to storiesOf storybook) diff --git a/components/x-styling-demo/storybook/styling.js b/components/x-styling-demo/storybook/styling.js deleted file mode 100644 index 3a0f07633..000000000 --- a/components/x-styling-demo/storybook/styling.js +++ /dev/null @@ -1,12 +0,0 @@ -exports.title = 'Styling' - -exports.data = { - danger: false, - large: false -} - -exports.knobs = ['danger', 'large'] - -// This reference is only required for hot module loading in development -// <https://webpack.js.org/concepts/hot-module-replacement/> -exports.m = module diff --git a/components/x-teaser-timeline/storybook/index.jsx b/components/x-teaser-timeline/storybook/index.jsx index e29c23561..0f28daf11 100644 --- a/components/x-teaser-timeline/storybook/index.jsx +++ b/components/x-teaser-timeline/storybook/index.jsx @@ -1,11 +1,8 @@ const { TeaserTimeline } = require('../dist/TeaserTimeline.cjs') import React from 'react' -import { storiesOf } from '@storybook/react' -import { withKnobs } from '@storybook/addon-knobs' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' -const pkg = require('../package.json') +const { args, argTypes } = require('./timeline') const dependencies = { 'o-normalise': '^1.6.0', @@ -13,22 +10,21 @@ const dependencies = { 'o-teaser': '^2.3.1' } -const knobs = require('./knobs') +export default { + title: 'x-teaser-timeline' +} + +export const Timeline = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Helmet> + <link rel="stylesheet" href={`components/x-teaser-timeline/dist/TeaserTimeline.css`} /> + </Helmet> + <TeaserTimeline {...args} /> + </div> + ) +} -storiesOf('x-teaser-timeline', module) - .addDecorator(withKnobs) - .add('Timeline', () => { - const { data, knobs: storyKnobs } = require('./timeline') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-teaser-timeline/${pkg.style}`} /> - </Helmet> - )} - <TeaserTimeline {...props} /> - </div> - ) - }) +Timeline.args = args +Timeline.argTypes = argTypes diff --git a/components/x-teaser-timeline/storybook/knobs.js b/components/x-teaser-timeline/storybook/knobs.js deleted file mode 100644 index 56cdc5055..000000000 --- a/components/x-teaser-timeline/storybook/knobs.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = (data, { number, select }) => { - return { - latestItemsTime() { - return select('Latest Items Time', { - None: '', - '2018-10-17T12:10:33.000Z': '2018-10-17T12:10:33.000Z' - }) - }, - customSlotPosition() { - return number('Custom Slot Position', data.customSlotPosition) - }, - customSlotContent() { - return select('Custom Slot Content', { - None: '', - Something: '---Custom slot content---' - }) - } - } -} diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index 209bf6d66..b0552f86f 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -1,13 +1,6 @@ const { Teaser } = require('../') import React from 'react' -import { storiesOf } from '@storybook/react' -import { withKnobs } from '@storybook/addon-knobs' -import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import createProps from '../../../.storybook/storybook.utils' -const path = require('path') -const pkg = require('../package.json') -const name = path.basename(pkg.name) const dependencies = { 'o-date': '^4.0.0', @@ -18,127 +11,104 @@ const dependencies = { 'o-video': '^6.0.0' } -const knobs = require('./knobs') - -storiesOf('x-teaser', module) - .addDecorator(withKnobs) - .add('Article', () => { - const { data, knobs: storyKnobs } = require('./article') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('Podcast', () => { - const { data, knobs: storyKnobs } = require('./podcast') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('Opinion', () => { - const { data, knobs: storyKnobs } = require('./opinion') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('ContentPackage', () => { - const { data, knobs: storyKnobs } = require('./content-package') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('PackageItem', () => { - const { data, knobs: storyKnobs } = require('./package-item') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('Promoted', () => { - const { data, knobs: storyKnobs } = require('./promoted') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('TopStory', () => { - const { data, knobs: storyKnobs } = require('./top-story') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) - .add('Video', () => { - const { data, knobs: storyKnobs } = require('./video') - const props = createProps(data, storyKnobs, knobs) - return ( - <div className="story-container"> - {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/${name}/${pkg.style}`} /> - </Helmet> - )} - <Teaser {...props} /> - </div> - ) - }) +const { argTypes } = require('./argTypes') + +export default { + title: 'x-teaser' +} + +export const Article = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Teaser {...args} /> + </div> + ) +} + +Article.args = require('./article').args +Article.argTypes = argTypes + +export const Podcast = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Teaser {...args} /> + </div> + ) +} +Podcast.args = require('./podcast').args +Podcast.argTypes = argTypes + +export const Opinion = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Teaser {...args} /> + </div> + ) +} +Opinion.args = require('./opinion').args +Opinion.argTypes = argTypes + +export const ContentPackage = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Teaser {...args} /> + </div> + ) +} + +ContentPackage.storyName = 'ContentPackage' +ContentPackage.args = require('./content-package').args +ContentPackage.argTypes = argTypes + +export const PackageItem = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Teaser {...args} /> + </div> + ) +} + +PackageItem.storyName = 'PackageItem' +PackageItem.args = require('./package-item').args +PackageItem.argTypes = argTypes + +export const Promoted = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Teaser {...args} /> + </div> + ) +} + +Promoted.args = require('./promoted').args +Promoted.argTypes = argTypes + +export const TopStory = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Teaser {...args} /> + </div> + ) +} + +TopStory.storyName = 'TopStory' +TopStory.args = require('./top-story').args +TopStory.argTypes = argTypes + +export const Video = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <Teaser {...args} /> + </div> + ) +} +Video.args = require('./video').args +Video.argTypes = argTypes From 1d2de756c61929dcc63f686f97e377f1fbc1fb1f Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Mon, 7 Dec 2020 14:32:14 +0000 Subject: [PATCH 643/760] Allow `next-live-event-api` endpoint URL to be overriden Make it easier for us to test the component locally. --- components/x-live-blog-wrapper/readme.md | 11 +++++++++++ .../x-live-blog-wrapper/src/LiveEventListener.js | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index b34696c93..da993d545 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -190,3 +190,14 @@ Feature | Type | Notes `showShareButtons` | Boolean | if `true` displays social media sharing buttons in posts `posts` | Array | Array of live blog post data `id` | String | **(required)** Unique id used for identifying the element in the document. + + +## Configuring the `next-live-event-api` endpoint URL. + +If you want to configure the URL for `next-live-event-api`, add the following plugin in your Webpack configuration file: + +```javascript +new webpack.DefinePlugin({ + LIVE_EVENT_API_URL: JSON.stringify('http://localhost:3003') +}) +``` \ No newline at end of file diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index 83217fdf9..a1fc1ca6a 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -67,7 +67,11 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, window.setTimeout(() => wrapper.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0) } - const eventSource = new EventSource(`https://next-live-event.ft.com/v2/liveblog/${liveBlogPackageUuid}`, { + // Allow `next-live-event-api` endpoint URL to be set in development. + const baseUrl = + typeof LIVE_EVENT_API_URL !== 'undefined' ? LIVE_EVENT_API_URL : 'https://next-live-event.ft.com' // eslint-disable-line no-undef + + const eventSource = new EventSource(`${baseUrl}/v2/liveblog/${liveBlogPackageUuid}`, { withCredentials: true }) From 261fb35fc9f9ac3ca10486c1255fa8e17defe484 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Mon, 7 Dec 2020 15:54:57 +0000 Subject: [PATCH 644/760] Remove redundant pkg.style checks --- .../x-privacy-manager/storybook/story-container.jsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/components/x-privacy-manager/storybook/story-container.jsx b/components/x-privacy-manager/storybook/story-container.jsx index 800c5fb00..c359ba518 100644 --- a/components/x-privacy-manager/storybook/story-container.jsx +++ b/components/x-privacy-manager/storybook/story-container.jsx @@ -2,7 +2,6 @@ import React from 'react' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' -import { default as pkg } from '../package.json' import { PrivacyManager } from '../src/privacy-manager' const dependencies = { @@ -18,11 +17,9 @@ export function StoryContainer(args) { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} - {pkg.style && ( - <Helmet> - <link rel="stylesheet" href={`components/x-privacy-manager/${pkg.style}`} /> - </Helmet> - )} + <Helmet> + <link rel="stylesheet" href={`components/x-privacy-manager/dist/privacy-manager.css`} /> + </Helmet> <div style={{ maxWidth: 740, margin: 'auto' }}> <PrivacyManager {...args} /> </div> From 3943e7aa14093ae60991a2a9b76232b4bebe4146 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Thu, 3 Dec 2020 16:02:28 +0000 Subject: [PATCH 645/760] fixes to make live-reloading work on storybook --- components/x-gift-article/src/GiftArticle.scss | 2 +- components/x-gift-article/src/MobileShareButtons.scss | 2 +- components/x-gift-article/storybook/index.jsx | 2 +- components/x-podcast-launchers/storybook/index.jsx | 2 +- components/x-styling-demo/storybook/index.jsx | 2 +- components/x-teaser-timeline/storybook/index.jsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index a5d5b7f56..6e2180a52 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -1,4 +1,4 @@ -@import 'src/lib/variables'; +@import './lib/variables'; @import 'o-normalise/main'; $o-buttons-is-silent: true; diff --git a/components/x-gift-article/src/MobileShareButtons.scss b/components/x-gift-article/src/MobileShareButtons.scss index 673f46fb8..a8d772f77 100644 --- a/components/x-gift-article/src/MobileShareButtons.scss +++ b/components/x-gift-article/src/MobileShareButtons.scss @@ -1,4 +1,4 @@ -@import 'src/lib/variables'; +@import './lib/variables'; @mixin shareButton($social-media-name, $background-color) { &, diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index 83c786ae0..c42641316 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -1,4 +1,4 @@ -const { GiftArticle } = require('../dist/GiftArticle.cjs') +import { GiftArticle } from '../src/GiftArticle' import fetchMock from 'fetch-mock' import React from 'react' import { Helmet } from 'react-helmet' diff --git a/components/x-podcast-launchers/storybook/index.jsx b/components/x-podcast-launchers/storybook/index.jsx index 30d727727..cca1e8691 100644 --- a/components/x-podcast-launchers/storybook/index.jsx +++ b/components/x-podcast-launchers/storybook/index.jsx @@ -1,4 +1,4 @@ -const { PodcastLaunchers } = require('../dist/PodcastLaunchers.cjs') +import { PodcastLaunchers } from '../src/PodcastLaunchers' const { brand } = require('@financial-times/n-concept-ids') import React from 'react' import { Helmet } from 'react-helmet' diff --git a/components/x-styling-demo/storybook/index.jsx b/components/x-styling-demo/storybook/index.jsx index 40758b852..d6c8d3949 100644 --- a/components/x-styling-demo/storybook/index.jsx +++ b/components/x-styling-demo/storybook/index.jsx @@ -1,4 +1,4 @@ -const { Button } = require('../dist/Button.cjs') +import { Button } from '../src/Button' import React from 'react' import { Helmet } from 'react-helmet' diff --git a/components/x-teaser-timeline/storybook/index.jsx b/components/x-teaser-timeline/storybook/index.jsx index 0f28daf11..70e5fca40 100644 --- a/components/x-teaser-timeline/storybook/index.jsx +++ b/components/x-teaser-timeline/storybook/index.jsx @@ -1,5 +1,5 @@ -const { TeaserTimeline } = require('../dist/TeaserTimeline.cjs') import React from 'react' +import { TeaserTimeline } from '../src/TeaserTimeline' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' const { args, argTypes } = require('./timeline') From 286c7d8b7e63969bf8f21692718bc3d899533c8a Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Fri, 4 Dec 2020 12:45:32 +0000 Subject: [PATCH 646/760] fixed teaserTimeline build errors by upgrading o-typography --- components/x-teaser-timeline/bower.json | 6 +++--- components/x-teaser-timeline/src/TeaserTimeline.scss | 11 ++++++----- components/x-teaser/storybook/index.jsx | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/components/x-teaser-timeline/bower.json b/components/x-teaser-timeline/bower.json index 65ae12d90..02cefaf5c 100644 --- a/components/x-teaser-timeline/bower.json +++ b/components/x-teaser-timeline/bower.json @@ -3,8 +3,8 @@ "private": true, "main": "dist/TeaserTimeline.es5.js", "dependencies": { - "o-colors": "^4.7.2", - "o-grid": "4.4.4", - "o-typography": "^5.7.5" + "o-typography": "^6.4.5", + "o-colors": "^5.3.0", + "o-grid": "^5.2.9" } } diff --git a/components/x-teaser-timeline/src/TeaserTimeline.scss b/components/x-teaser-timeline/src/TeaserTimeline.scss index 432b276f8..71e8652ef 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.scss +++ b/components/x-teaser-timeline/src/TeaserTimeline.scss @@ -20,8 +20,9 @@ } .itemGroup__heading { - @include oTypographySansBold($scale: 1); - @include oTypographyMargin($top: 2, $bottom: 2); + @include oTypographySans($scale: 1, $weight: 'bold'); + margin-top: oSpacingByName('s2'); + margin-bottom: oSpacingByName('s2'); @include oGridRespondTo($from: M) { grid-area: heading; @@ -33,7 +34,7 @@ .itemGroup__items { list-style-type: none; padding: 0; - @include oTypographyMargin($top: 2); + margin-top: oSpacingByName('s2'); @include oGridRespondTo($from: M) { grid-area: articles; @@ -46,12 +47,12 @@ display: flex; justify-content: space-between; border-bottom: 1px solid oColorsGetPaletteColor('black-20'); - @include oTypographyMargin($bottom: 2); + margin-bottom: oSpacingByName('s2'); :global { .o-teaser--timeline-teaser { border-bottom: 0; - @include oTypographyPadding($bottom: 0); + padding-bottom: 0; } } } diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index b0552f86f..57b6ae430 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -1,4 +1,4 @@ -const { Teaser } = require('../') +import { Teaser } from '../src/Teaser' import React from 'react' import BuildService from '../../../.storybook/build-service' From 9258195918e07354d81573fc952ef2850669ee3d Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Tue, 8 Dec 2020 09:26:38 +0000 Subject: [PATCH 647/760] x-teaser-timeline scss switch to oColorsByName --- components/x-teaser-timeline/src/TeaserTimeline.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/src/TeaserTimeline.scss b/components/x-teaser-timeline/src/TeaserTimeline.scss index 71e8652ef..c98f7d02b 100644 --- a/components/x-teaser-timeline/src/TeaserTimeline.scss +++ b/components/x-teaser-timeline/src/TeaserTimeline.scss @@ -46,7 +46,7 @@ .item { display: flex; justify-content: space-between; - border-bottom: 1px solid oColorsGetPaletteColor('black-20'); + border-bottom: 1px solid oColorsByName('black-20'); margin-bottom: oSpacingByName('s2'); :global { From be454f383cab2c3c6d54801cda30080be4ff8892 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Tue, 8 Dec 2020 16:15:21 +0000 Subject: [PATCH 648/760] storybook x-podcast-launchers use import instead of require --- components/x-podcast-launchers/storybook/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-podcast-launchers/storybook/index.jsx b/components/x-podcast-launchers/storybook/index.jsx index cca1e8691..bb706566f 100644 --- a/components/x-podcast-launchers/storybook/index.jsx +++ b/components/x-podcast-launchers/storybook/index.jsx @@ -1,5 +1,5 @@ import { PodcastLaunchers } from '../src/PodcastLaunchers' -const { brand } = require('@financial-times/n-concept-ids') +import { brand } from '@financial-times/n-concept-ids' import React from 'react' import { Helmet } from 'react-helmet' import BuildService from '../../../.storybook/build-service' From 2c8f8ab401cf4adffa98aff607d33196475efaf7 Mon Sep 17 00:00:00 2001 From: Glynn Phillips <glynndominicphillips@gmail.com> Date: Thu, 10 Dec 2020 09:09:35 +0000 Subject: [PATCH 649/760] Update breaking news label based on Spark data The data we get from Spark and UPP for breaking news is `standout.breakingNews` This change uses that property to render the breaking news label. For now it also still uses the `isBreakingNews` property that is used for wordpress data. --- components/x-live-blog-post/src/LiveBlogPost.jsx | 7 +++++-- .../x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx | 4 +++- components/x-live-blog-post/storybook/index.jsx | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index c9d7c5b1c..5f67ce4f9 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -12,11 +12,14 @@ const LiveBlogPost = (props) => { bodyHTML, publishedTimestamp, // Remove once wordpress is no longer in use publishedDate, - isBreakingNews, + isBreakingNews, // Remove once wordpress is no longer in use + standout = {}, articleUrl, showShareButtons = false } = props + const showBreakingNewsLabel = standout.breakingNews || isBreakingNews + return ( <article className={`live-blog-post ${styles['live-blog-post']}`} @@ -26,7 +29,7 @@ const LiveBlogPost = (props) => { <div className="live-blog-post__meta"> <Timestamp publishedTimestamp={publishedDate || publishedTimestamp} /> </div> - {isBreakingNews && <div className={styles['live-blog-post__breaking-news']}>Breaking news</div>} + {showBreakingNewsLabel && <div className={styles['live-blog-post__breaking-news']}>Breaking news</div>} {title && <h1 className={styles['live-blog-post__title']}>{title}</h1>} <div className={`${styles['live-blog-post__body']} n-content-body article--body`} diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index 256d35d1d..71c2c2769 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -28,7 +28,9 @@ const breakingNewsSpark = { title: 'Test', bodyHTML: '<p>Test</p>', publishedDate: new Date().toISOString(), - isBreakingNews: true, + standout: { + breakingNews: true + }, articleUrl: 'Https://www.ft.com', showShareButtons: true } diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index 2108130ee..4c188753d 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -15,6 +15,9 @@ export const ContentBody = (args) => { ContentBody.args = { title: 'Turkey’s virus deaths may be 25% higher than official figure', isBreakingNews: false, + standout: { + breakingNews: false + }, bodyHTML: '<p>Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.</p>\n<p>Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.</p>\n<p>But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.</p>\n<p><a href="https://www.ft.com/content/80bb222c-b6eb-40ea-8014-563cbe9e0117" target="_blank">Read the article here</a></p>\n<p><img class="picture" src="http://blogs.ft.com/the-world/files/2020/05/istanbul_excess_morts_l.jpg"></p>', id: '12345', From b821ea6251795faa04fa6196c09eb9ab260d086a Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@ft.com> Date: Wed, 23 Dec 2020 13:08:57 +0000 Subject: [PATCH 650/760] use ci helper to publish to github pages --- .circleci/config.yml | 4 ++-- private/scripts/gh-pages | 39 --------------------------------------- 2 files changed, 2 insertions(+), 41 deletions(-) delete mode 100755 private/scripts/gh-pages diff --git a/.circleci/config.yml b/.circleci/config.yml index bf49dbae9..dad3d93c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -171,8 +171,8 @@ jobs: name: Build documentation website command: npm run build-docs - run: - name: Publish GitHub Pages - command: ./private/scripts/gh-pages + name: shared-helper / publish-github-pages + command: .circleci/shared-helpers/helper-publish-github-pages workflows: diff --git a/private/scripts/gh-pages b/private/scripts/gh-pages deleted file mode 100755 index 4c9e9614c..000000000 --- a/private/scripts/gh-pages +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -TARGET_DIR=web/public/* -TARGET_BRANCH=gh-pages -TEMP_DIR=tmp - -# Set error handling -set -eu -o pipefail - -# Set GitHub user information (the email must match the SSH key provided to Circle) -git config --global user.email $GITHUB_EMAIL -git config --global user.name $GITHUB_NAME - -# HACK: Add GitHub to known hosts to avoid an interactive prompt when cloning over SSH -mkdir -p ~/.ssh -ssh-keyscan -H github.com >> ~/.ssh/known_hosts - -# Clone only the branch we need so we don't download all of the project history -git clone $CIRCLE_REPOSITORY_URL $TEMP_DIR --single-branch --branch $TARGET_BRANCH - -# Remove all of the files, -q prevents logging every filename -cd $TEMP_DIR -git rm -rf . -cd .. - -# Copy contents of target directory to the deployment directory -cp -R -L $TARGET_DIR $TEMP_DIR - -# Copy CI config (which should instruct Circle to ignore this branch) -cp -r .circleci $TEMP_DIR - -cd $TEMP_DIR - -# Stage and commit all of the files -git add -A &> /dev/null -git commit -m "Automated deployment to GitHub Pages: ${CIRCLE_SHA1}" --allow-empty - -# Push to the target branch, staying quiet unless something goes wrong -git push -q origin $TARGET_BRANCH From 888c211586c6d5639b5341010db8401a806529bd Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@gmail.com> Date: Wed, 23 Dec 2020 15:12:52 +0000 Subject: [PATCH 651/760] Revert "CPP-344 use ci helper to publish to github pages" --- .circleci/config.yml | 4 ++-- private/scripts/gh-pages | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100755 private/scripts/gh-pages diff --git a/.circleci/config.yml b/.circleci/config.yml index dad3d93c6..bf49dbae9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -171,8 +171,8 @@ jobs: name: Build documentation website command: npm run build-docs - run: - name: shared-helper / publish-github-pages - command: .circleci/shared-helpers/helper-publish-github-pages + name: Publish GitHub Pages + command: ./private/scripts/gh-pages workflows: diff --git a/private/scripts/gh-pages b/private/scripts/gh-pages new file mode 100755 index 000000000..4c9e9614c --- /dev/null +++ b/private/scripts/gh-pages @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +TARGET_DIR=web/public/* +TARGET_BRANCH=gh-pages +TEMP_DIR=tmp + +# Set error handling +set -eu -o pipefail + +# Set GitHub user information (the email must match the SSH key provided to Circle) +git config --global user.email $GITHUB_EMAIL +git config --global user.name $GITHUB_NAME + +# HACK: Add GitHub to known hosts to avoid an interactive prompt when cloning over SSH +mkdir -p ~/.ssh +ssh-keyscan -H github.com >> ~/.ssh/known_hosts + +# Clone only the branch we need so we don't download all of the project history +git clone $CIRCLE_REPOSITORY_URL $TEMP_DIR --single-branch --branch $TARGET_BRANCH + +# Remove all of the files, -q prevents logging every filename +cd $TEMP_DIR +git rm -rf . +cd .. + +# Copy contents of target directory to the deployment directory +cp -R -L $TARGET_DIR $TEMP_DIR + +# Copy CI config (which should instruct Circle to ignore this branch) +cp -r .circleci $TEMP_DIR + +cd $TEMP_DIR + +# Stage and commit all of the files +git add -A &> /dev/null +git commit -m "Automated deployment to GitHub Pages: ${CIRCLE_SHA1}" --allow-empty + +# Push to the target branch, staying quiet unless something goes wrong +git push -q origin $TARGET_BRANCH From 95763c95f8ee46df8fdef0a5900981a4b33decb7 Mon Sep 17 00:00:00 2001 From: Estefania Morton <estefania.morton@gmail.com> Date: Thu, 24 Dec 2020 10:40:50 +0000 Subject: [PATCH 652/760] Revert "Revert "CPP-344 use ci helper to publish to github pages"" --- .circleci/config.yml | 4 ++-- private/scripts/gh-pages | 39 --------------------------------------- 2 files changed, 2 insertions(+), 41 deletions(-) delete mode 100755 private/scripts/gh-pages diff --git a/.circleci/config.yml b/.circleci/config.yml index bf49dbae9..dad3d93c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -171,8 +171,8 @@ jobs: name: Build documentation website command: npm run build-docs - run: - name: Publish GitHub Pages - command: ./private/scripts/gh-pages + name: shared-helper / publish-github-pages + command: .circleci/shared-helpers/helper-publish-github-pages workflows: diff --git a/private/scripts/gh-pages b/private/scripts/gh-pages deleted file mode 100755 index 4c9e9614c..000000000 --- a/private/scripts/gh-pages +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -TARGET_DIR=web/public/* -TARGET_BRANCH=gh-pages -TEMP_DIR=tmp - -# Set error handling -set -eu -o pipefail - -# Set GitHub user information (the email must match the SSH key provided to Circle) -git config --global user.email $GITHUB_EMAIL -git config --global user.name $GITHUB_NAME - -# HACK: Add GitHub to known hosts to avoid an interactive prompt when cloning over SSH -mkdir -p ~/.ssh -ssh-keyscan -H github.com >> ~/.ssh/known_hosts - -# Clone only the branch we need so we don't download all of the project history -git clone $CIRCLE_REPOSITORY_URL $TEMP_DIR --single-branch --branch $TARGET_BRANCH - -# Remove all of the files, -q prevents logging every filename -cd $TEMP_DIR -git rm -rf . -cd .. - -# Copy contents of target directory to the deployment directory -cp -R -L $TARGET_DIR $TEMP_DIR - -# Copy CI config (which should instruct Circle to ignore this branch) -cp -r .circleci $TEMP_DIR - -cd $TEMP_DIR - -# Stage and commit all of the files -git add -A &> /dev/null -git commit -m "Automated deployment to GitHub Pages: ${CIRCLE_SHA1}" --allow-empty - -# Push to the target branch, staying quiet unless something goes wrong -git push -q origin $TARGET_BRANCH From 15eddd1741e87ba405acd9f73958a0eaa161c92a Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Tue, 22 Dec 2020 17:12:35 +0000 Subject: [PATCH 653/760] Render a live blog post author Spark now has a `byline` field which is used to enter a post author. This property is passed down to `next-article` and `ft-app`. From there, it's passed down to `x-live-blog-wrapper` and eventually to `x-live-blog-post` which is responsible for rendering the author. --- components/x-live-blog-post/src/LiveBlogPost.jsx | 4 +++- components/x-live-blog-post/src/LiveBlogPost.scss | 8 ++++++++ .../x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 5f67ce4f9..e7b809f43 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -15,7 +15,8 @@ const LiveBlogPost = (props) => { isBreakingNews, // Remove once wordpress is no longer in use standout = {}, articleUrl, - showShareButtons = false + showShareButtons = false, + byline } = props const showBreakingNewsLabel = standout.breakingNews || isBreakingNews @@ -31,6 +32,7 @@ const LiveBlogPost = (props) => { </div> {showBreakingNewsLabel && <div className={styles['live-blog-post__breaking-news']}>Breaking news</div>} {title && <h1 className={styles['live-blog-post__title']}>{title}</h1>} + {byline && <p className={styles['live-blog-post__byline']}>{byline}</p>} <div className={`${styles['live-blog-post__body']} n-content-body article--body`} dangerouslySetInnerHTML={{ __html: bodyHTML || content }} diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 427686126..afd0e140e 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -13,12 +13,20 @@ .live-blog-post__title { @include oTypographyDisplay($scale: 5); margin-top: oSpacingByName('s4'); + margin-bottom: oSpacingByName('s2'); } .live-blog-post__breaking-news + .live-blog-post__title { margin-top: oSpacingByName('s1'); } +.live-blog-post__byline { + @include oTypographySans($scale: -1, $weight: 'semibold'); + color: oColorsByName('slate'); + margin-top: oSpacingByName('s2'); + margin-bottom: oSpacingByName('s4'); +} + .live-blog-post__body { @include oTypographySerif($scale: 1, $line-height: 1.55); margin-top: oSpacingByName('s6'); diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index 71c2c2769..28cab6c29 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -26,6 +26,7 @@ const regularPostWordpress = { const breakingNewsSpark = { id: '12345', title: 'Test', + byline: 'Test author', bodyHTML: '<p>Test</p>', publishedDate: new Date().toISOString(), standout: { @@ -38,6 +39,7 @@ const breakingNewsSpark = { const regularPostSpark = { id: '12345', title: 'Test title', + byline: 'Test author', bodyHTML: '<p><i>Test body</i></p>', publishedDate: new Date().toISOString(), isBreakingNews: false, @@ -76,6 +78,12 @@ describe('x-live-blog-post', () => { expect(liveBlogPost.html()).toContain(regularPostSpark.publishedTimestamp) }) + it('renders byline', () => { + const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) + + expect(liveBlogPost.html()).toContain('Test author') + }) + it('renders sharing buttons', () => { const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) From ba7550dfaecc87a360a588c6ef1baa954286865f Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Tue, 22 Dec 2020 17:03:12 +0000 Subject: [PATCH 654/760] Update Storybook --- components/x-live-blog-post/storybook/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index 4c188753d..ffd09a425 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -14,6 +14,7 @@ export const ContentBody = (args) => { ContentBody.args = { title: 'Turkey’s virus deaths may be 25% higher than official figure', + byline: 'George Russell', isBreakingNews: false, standout: { breakingNews: false From 6e1235e6ad4a69d68ef5e100745342cb90001e99 Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Tue, 22 Dec 2020 17:05:32 +0000 Subject: [PATCH 655/760] Use `oTypographySans` mixin for font size `oTypographySans` can be passed various parameters, including `scale` (font size and height) and `weight`. When using this mixin, there's no need to set the font size using `font-size`. We can use the `scale` parameter instead. This commit removes `font-size` and uses the correct `scale`. To see what scale corresponds to what font-size and line-height combination, see https://registry.origami.ft.com/components/o-typography@6.4.5/readme?brand=master. --- components/x-live-blog-post/src/LiveBlogPost.scss | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index afd0e140e..0093357f3 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -37,14 +37,12 @@ } .live-blog-post__timestamp { - @include oTypographySans($scale:0, $weight: 'semibold'); - font-size: 14px; + @include oTypographySans($scale: -1, $weight: 'semibold'); text-transform: uppercase; } .live-blog-post__timestamp-exact-time { - @include oTypographySans($scale:0, $weight: 'light'); - font-size: 14px; + @include oTypographySans($scale: -1, $weight: 'light'); color: oColorsMix(black, paper, 60); padding-left: oSpacingByName('s2'); } @@ -61,8 +59,7 @@ } .live-blog-post__breaking-news { - @include oTypographySans($scale:0); - font-size: 12px; + @include oTypographySans($scale: -2); color: oColorsByName('crimson'); text-transform: uppercase; margin-top: oSpacingByName('s4'); From a7ad77eecc3a48fca3b122a9f0ceee069c1397ea Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Tue, 22 Dec 2020 17:04:53 +0000 Subject: [PATCH 656/760] Whitespace linting --- components/x-live-blog-post/src/LiveBlogPost.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 0093357f3..6a7b13e13 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -7,7 +7,7 @@ border-bottom: 1px solid oColorsMix(black, paper, 20); margin-top: oSpacingByName('s8'); color: oColorsMix(black, paper, 90); - padding-bottom: oSpacingByName('s8'); + padding-bottom: oSpacingByName('s8'); } .live-blog-post__title { From 655c596aa85aa589770ce9aaa25d96ab4ca963c2 Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Tue, 5 Jan 2021 09:21:22 +0000 Subject: [PATCH 657/760] Update byline styling As requested by Josh (Designer) --- components/x-live-blog-post/src/LiveBlogPost.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 6a7b13e13..eb387f408 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -13,7 +13,7 @@ .live-blog-post__title { @include oTypographyDisplay($scale: 5); margin-top: oSpacingByName('s4'); - margin-bottom: oSpacingByName('s2'); + margin-bottom: oSpacingByName('s1'); } .live-blog-post__breaking-news + .live-blog-post__title { @@ -22,9 +22,9 @@ .live-blog-post__byline { @include oTypographySans($scale: -1, $weight: 'semibold'); - color: oColorsByName('slate'); - margin-top: oSpacingByName('s2'); - margin-bottom: oSpacingByName('s4'); + color: oColorsMix(black, paper, 70); + margin-top: oSpacingByName('s1'); + margin-bottom: 20px; } .live-blog-post__body { From 913861b79be90751226dd9e3416683ff0a2e8a4e Mon Sep 17 00:00:00 2001 From: Keran Braich <keran.braich@ft.com> Date: Tue, 5 Jan 2021 09:21:58 +0000 Subject: [PATCH 658/760] Add padding between timestamp and underline Josh made a cheeky side request to add padding and for simplicity, I've tacked it onto this pull request. --- components/x-live-blog-post/src/LiveBlogPost.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index eb387f408..5d28fc293 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -52,6 +52,7 @@ display: block; width: oSpacingByName('s4'); border-bottom: 4px solid oColorsMix(black, paper, 90); + padding-top: oSpacingByName('s1'); } .live-blog-post__share-buttons { From b9923027c14c6cf4be18249a3cdf5f8439c8fab8 Mon Sep 17 00:00:00 2001 From: Jennifer Johnson <jkerr321@gmail.com> Date: Wed, 20 Jan 2021 16:43:41 +0000 Subject: [PATCH 659/760] Check for existance of image as soon as function starts So that the useImageService const definition doesn't error if there is no image present. When this errors on the home page the whole homepage returns a 500. --- components/x-teaser/src/Image.jsx | 46 +++++++++++++++++-------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 79e0fa704..2af4b8384 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -25,26 +25,30 @@ const LazyImage = ({ src, lazyLoad }) => { } export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, imageHighestQuality, ...props }) => { - const displayUrl = relativeUrl || url - const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')) - const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {} - const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize], options) : image.url - const ImageComponent = imageLazyLoad ? LazyImage : NormalImage + if (image) { + const displayUrl = relativeUrl || url + const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')) + const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {} + const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize], options) : image.url + const ImageComponent = imageLazyLoad ? LazyImage : NormalImage - return image ? ( - <div className="o-teaser__image-container js-teaser-image-container"> - <Link - {...props} - url={displayUrl} - attrs={{ - 'data-trackable': 'image-link', - tabIndex: '-1', - 'aria-hidden': 'true' - }}> - <div className="o-teaser__image-placeholder" style={{ paddingBottom: aspectRatio(image) }}> - <ImageComponent src={imageSrc} lazyLoad={imageLazyLoad} /> - </div> - </Link> - </div> - ) : null + return ( + <div className="o-teaser__image-container js-teaser-image-container"> + <Link + {...props} + url={displayUrl} + attrs={{ + 'data-trackable': 'image-link', + tabIndex: '-1', + 'aria-hidden': 'true' + }}> + <div className="o-teaser__image-placeholder" style={{ paddingBottom: aspectRatio(image) }}> + <ImageComponent src={imageSrc} lazyLoad={imageLazyLoad} /> + </div> + </Link> + </div> + ) + } else { + return null + } } From ae1cb49a2fffcc5c86d6e72fc3641d046f76e635 Mon Sep 17 00:00:00 2001 From: Jennifer Johnson <jkerr321@gmail.com> Date: Thu, 21 Jan 2021 12:03:20 +0000 Subject: [PATCH 660/760] Use early return when checking for image --- components/x-teaser/src/Image.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 2af4b8384..2aef9cd70 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -25,7 +25,9 @@ const LazyImage = ({ src, lazyLoad }) => { } export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, imageHighestQuality, ...props }) => { - if (image) { + if (!image) { + return null + } else { const displayUrl = relativeUrl || url const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')) const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {} @@ -48,7 +50,5 @@ export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, imageHighes </Link> </div> ) - } else { - return null } } From 42da1eaeb6d76c9d1fab3f6e3b4a2840fefd3bb3 Mon Sep 17 00:00:00 2001 From: Jennifer Johnson <jkerr321@gmail.com> Date: Thu, 21 Jan 2021 13:13:14 +0000 Subject: [PATCH 661/760] check for image url and create snapshot test --- .../article-with-missing-image-url.json | 34 ++ .../__snapshots__/snapshots.test.js.snap | 375 ++++++++++++++++++ .../x-teaser/__tests__/snapshots.test.js | 1 + components/x-teaser/src/Image.jsx | 2 +- 4 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 components/x-teaser/__fixtures__/article-with-missing-image-url.json diff --git a/components/x-teaser/__fixtures__/article-with-missing-image-url.json b/components/x-teaser/__fixtures__/article-with-missing-image-url.json new file mode 100644 index 000000000..792895849 --- /dev/null +++ b/components/x-teaser/__fixtures__/article-with-missing-image-url.json @@ -0,0 +1,34 @@ +{ + "type": "article", + "id": "", + "url": "#", + "title": "Inside charity fundraiser where hostesses are put on show", + "altTitle": "Men Only, the charity fundraiser with hostesses on show", + "standfirst": "FT investigation finds groping and sexual harassment at secretive black-tie dinner", + "altStandfirst": "Groping and sexual harassment at black-tie dinner charity event", + "publishedDate": "2018-01-23T15:07:00.000Z", + "firstPublishedDate": "2018-01-23T13:53:00.000Z", + "metaPrefixText": "", + "metaSuffixText": "", + "metaLink": { + "url": "#", + "prefLabel": "Sexual misconduct allegations" + }, + "metaAltLink": { + "url": "#", + "prefLabel": "FT Investigations" + }, + "image": { + "width": 2048, + "height": 1152 + }, + "indicators": { + "isEditorsChoice": true + }, + "status": "", + "headshotTint": "", + "accessLevel": "free", + "theme": "", + "parentTheme": "", + "modifiers": "" +} diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 2f3f73903..63dfcb7ad 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -120,6 +120,41 @@ exports[`x-teaser / snapshots renders a Hero teaser with article-with-data-image </div> `; +exports[`x-teaser / snapshots renders a Hero teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a Hero teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre o-teaser--has-image js-teaser" @@ -638,6 +673,53 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with article-with-data </div> `; +exports[`x-teaser / snapshots renders a HeroNarrow teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a HeroNarrow teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre js-teaser" @@ -1116,6 +1198,41 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with article-with-dat </div> `; +exports[`x-teaser / snapshots renders a HeroOverlay teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--hero-image o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a HeroOverlay teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--hero o-teaser--hero-image o-teaser--has-image js-teaser" @@ -1610,6 +1727,41 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with article-with-data- </div> `; +exports[`x-teaser / snapshots renders a HeroVideo teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--hero o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a HeroVideo teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--hero o-teaser--centre js-teaser" @@ -2084,6 +2236,53 @@ exports[`x-teaser / snapshots renders a Large teaser with article-with-data-imag </div> `; +exports[`x-teaser / snapshots renders a Large teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--large o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a Large teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--large o-teaser--centre o-teaser--has-image js-teaser" @@ -2662,6 +2861,41 @@ exports[`x-teaser / snapshots renders a Small teaser with article-with-data-imag </div> `; +exports[`x-teaser / snapshots renders a Small teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a Small teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--small o-teaser--centre js-teaser" @@ -3080,6 +3314,53 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with article-with-data </div> `; +exports[`x-teaser / snapshots renders a SmallHeavy teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--small o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a SmallHeavy teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--small o-teaser--centre o-teaser--has-image js-teaser" @@ -3682,6 +3963,53 @@ exports[`x-teaser / snapshots renders a TopStory teaser with article-with-data-i </div> `; +exports[`x-teaser / snapshots renders a TopStory teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a TopStory teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--top-story o-teaser--centre js-teaser" @@ -4221,6 +4549,53 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article-wi </div> `; +exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with article-with-missing-image-url data 1`] = ` +<div + className="o-teaser o-teaser--article o-teaser--top-story o-teaser--landscape o-teaser--highlight js-teaser" + data-id="" +> + <div + className="o-teaser__content" + > + <div + className="o-teaser__meta" + > + <a + aria-label="Category: Sexual misconduct allegations" + className="o-teaser__tag" + data-trackable="teaser-tag" + href="#" + > + Sexual misconduct allegations + </a> + </div> + <div + className="o-teaser__heading" + > + <a + className="js-teaser-heading-link" + data-trackable="heading-link" + href="#" + > + Inside charity fundraiser where hostesses are put on show + </a> + </div> + <p + className="o-teaser__standfirst" + > + <a + className="js-teaser-standfirst-link" + data-trackable="standfirst-link" + href="#" + tabIndex={-1} + > + FT investigation finds groping and sexual harassment at secretive black-tie dinner + </a> + </p> + </div> +</div> +`; + exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with contentPackage data 1`] = ` <div className="o-teaser o-teaser--package o-teaser--top-story o-teaser--landscape o-teaser--has-image js-teaser" diff --git a/components/x-teaser/__tests__/snapshots.test.js b/components/x-teaser/__tests__/snapshots.test.js index 182ea7993..cdd0b2ddf 100644 --- a/components/x-teaser/__tests__/snapshots.test.js +++ b/components/x-teaser/__tests__/snapshots.test.js @@ -5,6 +5,7 @@ const { Teaser, presets } = require('../') const storyData = { article: require('../__fixtures__/article.json'), 'article-with-data-image': require('../__fixtures__/article-with-data-image.json'), + 'article-with-missing-image-url': require('../__fixtures__/article-with-missing-image-url.json'), opinion: require('../__fixtures__/opinion.json'), contentPackage: require('../__fixtures__/content-package.json'), packageItem: require('../__fixtures__/package-item.json'), diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 2aef9cd70..2f101edc4 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -25,7 +25,7 @@ const LazyImage = ({ src, lazyLoad }) => { } export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, imageHighestQuality, ...props }) => { - if (!image) { + if (!image || (image && !image.url)) { return null } else { const displayUrl = relativeUrl || url From 05f55041bda479e52ab3f5432c9d3696c6724f0e Mon Sep 17 00:00:00 2001 From: Jennifer Johnson <jkerr321@gmail.com> Date: Thu, 21 Jan 2021 13:59:02 +0000 Subject: [PATCH 662/760] remove unnecessary else --- components/x-teaser/src/Image.jsx | 45 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/components/x-teaser/src/Image.jsx b/components/x-teaser/src/Image.jsx index 2f101edc4..18988565d 100644 --- a/components/x-teaser/src/Image.jsx +++ b/components/x-teaser/src/Image.jsx @@ -27,28 +27,27 @@ const LazyImage = ({ src, lazyLoad }) => { export default ({ relativeUrl, url, image, imageSize, imageLazyLoad, imageHighestQuality, ...props }) => { if (!image || (image && !image.url)) { return null - } else { - const displayUrl = relativeUrl || url - const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')) - const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {} - const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize], options) : image.url - const ImageComponent = imageLazyLoad ? LazyImage : NormalImage - - return ( - <div className="o-teaser__image-container js-teaser-image-container"> - <Link - {...props} - url={displayUrl} - attrs={{ - 'data-trackable': 'image-link', - tabIndex: '-1', - 'aria-hidden': 'true' - }}> - <div className="o-teaser__image-placeholder" style={{ paddingBottom: aspectRatio(image) }}> - <ImageComponent src={imageSrc} lazyLoad={imageLazyLoad} /> - </div> - </Link> - </div> - ) } + const displayUrl = relativeUrl || url + const useImageService = !(image.url.startsWith('data:') || image.url.startsWith('blob:')) + const options = imageSize === 'XXL' && imageHighestQuality ? { quality: 'highest' } : {} + const imageSrc = useImageService ? imageService(image.url, ImageSizes[imageSize], options) : image.url + const ImageComponent = imageLazyLoad ? LazyImage : NormalImage + + return ( + <div className="o-teaser__image-container js-teaser-image-container"> + <Link + {...props} + url={displayUrl} + attrs={{ + 'data-trackable': 'image-link', + tabIndex: '-1', + 'aria-hidden': 'true' + }}> + <div className="o-teaser__image-placeholder" style={{ paddingBottom: aspectRatio(image) }}> + <ImageComponent src={imageSrc} lazyLoad={imageLazyLoad} /> + </div> + </Link> + </div> + ) } From e81030d322220f1607897218b1708c1bc2764323 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Wed, 20 Jan 2021 15:22:49 +0000 Subject: [PATCH 663/760] Remove update and delete event handling We're going to focus on inserted posts for now. --- components/x-live-blog-wrapper/readme.md | 17 -------------- .../src/LiveBlogWrapper.jsx | 22 ------------------ .../src/LiveEventListener.js | 23 ------------------- .../src/__tests__/LiveBlogWrapper.test.jsx | 23 ------------------- 4 files changed, 85 deletions(-) diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index da993d545..b2d49ac43 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -165,23 +165,6 @@ wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', // LiveBlogPost element }); -wrapperElement.addEventListener('LiveBlogWrapper.DELETE_POST', - (ev) => { - const { postId } = ev.detail; - - // postId can be used to identify the deleted - // LiveBlogPost element - }); - -wrapperElement.addEventListener('LiveBlogWrapper.UPDATE_POST', - (ev) => { - const { post } = ev.detail; - - // post object contains data about a live blog post - // post.postId can be used to identify the newly rendered - // LiveBlogPost element - }); -``` ### Properties Feature | Type | Notes diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 74bf8ca35..4823e9b43 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -8,28 +8,6 @@ const withLiveBlogWrapperActions = withActions({ return (props) => { props.posts.unshift(post) - return props - } - }, - - updatePost(updated) { - return (props) => { - const index = props.posts.findIndex((post) => post.id === updated.postId) - if (index >= 0) { - props.posts[index] = updated - } - - return props - } - }, - - deletePost(postId) { - return (props) => { - const index = props.posts.findIndex((post) => post.id === postId) - if (index >= 0) { - props.posts.splice(index, 1) - } - return props } } diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index a1fc1ca6a..a16d45867 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -85,29 +85,6 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, invokeAction('insertPost', [post]) dispatchLiveUpdateEvent('LiveBlogWrapper.INSERT_POST', { post }) }) - - eventSource.addEventListener('update-post', (event) => { - const post = parsePost(event) - - if (!post) { - return - } - - invokeAction('updatePost', [post]) - dispatchLiveUpdateEvent('LiveBlogWrapper.UPDATE_POST', { post }) - }) - - eventSource.addEventListener('delete-post', (event) => { - const post = parsePost(event) - - if (!post) { - return - } - - const postId = post.postId - invokeAction('deletePost', [postId]) - dispatchLiveUpdateEvent('LiveBlogWrapper.DELETE_POST', { postId }) - }) } export { listenToLiveBlogEvents } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 826e4cfa3..c3fe09660 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -73,27 +73,4 @@ describe('liveBlogWrapperActions', () => { expect(posts.length).toEqual(3) expect(posts[0].id).toEqual('3') }) - - it('updates a post', () => { - const updatedPost2 = { - postId: '2', - title: 'Updated title' - } - - // updatePost function returns another function that takes the list of component props - // as an argument and returns the updated props. - actions.updatePost(updatedPost2)({ posts }) - - expect(posts.length).toEqual(2) - expect(posts[1].title).toEqual('Updated title') - }) - - it('deletes a post', () => { - // deletePost function returns another function that takes the list of component props - // as an argument and returns the updated props. - actions.deletePost('1')({ posts }) - - expect(posts.length).toEqual(1) - expect(posts[0].id).toEqual('2') - }) }) From e73ed8d2124d68f33fb9f07007a536fa8e493710 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Thu, 21 Jan 2021 11:12:39 +0000 Subject: [PATCH 664/760] Check if post exists before inserting This will help us avoid duplicates that may be sent. --- .../x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 7 +++++-- .../src/__tests__/LiveBlogWrapper.test.jsx | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 4823e9b43..cd916e050 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -4,9 +4,12 @@ import { withActions } from '@financial-times/x-interaction' import { listenToLiveBlogEvents } from './LiveEventListener' const withLiveBlogWrapperActions = withActions({ - insertPost(post) { + insertPost(newPost) { return (props) => { - props.posts.unshift(post) + const newPostAlreadyExists = props.posts.find((post) => post.id === newPost.id) + if (!newPostAlreadyExists) { + props.posts.unshift(newPost) + } return props } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index c3fe09660..218deb17f 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -73,4 +73,16 @@ describe('liveBlogWrapperActions', () => { expect(posts.length).toEqual(3) expect(posts[0].id).toEqual('3') }) + + it('does not insert a new post if a duplicate', () => { + // Clone an existing post to check if gets inserted again. + const duplicatePost = { ...posts[0] } + + // insertPost function returns another function that takes the list of component props + // as an argument and returns the updated props. + actions.insertPost(duplicatePost)({ posts }) + + expect(posts.length).toEqual(2) + expect(posts[0].id).toEqual('1') + }) }) From eb32202feaf1b2cb7cc7d037321fdb401fbf1e00 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Thu, 21 Jan 2021 11:40:43 +0000 Subject: [PATCH 665/760] Add normalisePost helper Allow us to handle incoming WordPress events which use 'postId', we can remove this logic in the future. --- components/x-live-blog-wrapper/readme.md | 2 +- .../src/LiveBlogWrapper.jsx | 6 ++-- .../src/LiveEventListener.js | 7 +++-- .../src/__tests__/normalisePost.test.js | 28 +++++++++++++++++++ .../x-live-blog-wrapper/src/normalisePost.js | 9 ++++++ 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 components/x-live-blog-wrapper/src/__tests__/normalisePost.test.js create mode 100644 components/x-live-blog-wrapper/src/normalisePost.js diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index b2d49ac43..537b40a76 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -161,7 +161,7 @@ wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', const { post } = ev.detail; // post object contains data about a live blog post - // post.postId can be used to identify the newly rendered + // post.id can be used to identify the newly rendered // LiveBlogPost element }); diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index cd916e050..5a5be14db 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -2,13 +2,15 @@ import { h } from '@financial-times/x-engine' import { LiveBlogPost } from '@financial-times/x-live-blog-post' import { withActions } from '@financial-times/x-interaction' import { listenToLiveBlogEvents } from './LiveEventListener' +import { normalisePost } from './normalisePost' const withLiveBlogWrapperActions = withActions({ insertPost(newPost) { return (props) => { - const newPostAlreadyExists = props.posts.find((post) => post.id === newPost.id) + const normalisedNewPost = normalisePost(newPost) + const newPostAlreadyExists = props.posts.find((post) => post.id === normalisedNewPost.id) if (!newPostAlreadyExists) { - props.posts.unshift(newPost) + props.posts.unshift(normalisedNewPost) } return props diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index a16d45867..56ea2e438 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -1,11 +1,14 @@ +import { normalisePost } from './normalisePost' + const parsePost = (event) => { const post = JSON.parse(event.data) + const normalisedPost = normalisePost(post) - if (!post || !post.postId) { + if (!normalisedPost || !normalisedPost.id) { return } - return post + return normalisedPost } const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, actions }) => { diff --git a/components/x-live-blog-wrapper/src/__tests__/normalisePost.test.js b/components/x-live-blog-wrapper/src/__tests__/normalisePost.test.js new file mode 100644 index 000000000..6451c0718 --- /dev/null +++ b/components/x-live-blog-wrapper/src/__tests__/normalisePost.test.js @@ -0,0 +1,28 @@ +import { normalisePost } from '../normalisePost' + +describe('normalisePost', () => { + it('does nothing if no post', () => { + expect(normalisePost({})).toEqual({}) + }) + it('does not change id property if id property exists', () => { + expect( + normalisePost({ + id: 123, + postId: 456 + }) + ).toEqual({ + id: 123, + postId: 456 + }) + }) + it('adds id property if doesnt it exist but postId property does', () => { + expect( + normalisePost({ + postId: 456 + }) + ).toEqual({ + id: 456, + postId: 456 + }) + }) +}) diff --git a/components/x-live-blog-wrapper/src/normalisePost.js b/components/x-live-blog-wrapper/src/normalisePost.js new file mode 100644 index 000000000..d628d5582 --- /dev/null +++ b/components/x-live-blog-wrapper/src/normalisePost.js @@ -0,0 +1,9 @@ +// Remove this helper when we no longer need to handle incoming WordPress events. +const normalisePost = (post) => { + if (post && !post.id && post.postId) { + post.id = post.postId + } + return post +} + +export { normalisePost } From 3a82defe3b0a9a244ab9191156b008e9365f9590 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Thu, 21 Jan 2021 15:15:39 +0000 Subject: [PATCH 666/760] Dispatch events only when changes We need to make sure that we only trigger the INSERT_POST event when we actually insert a new post. --- .../src/LiveBlogWrapper.jsx | 4 +- .../src/LiveEventListener.js | 33 +-------------- .../src/__tests__/LiveBlogWrapper.test.jsx | 16 +++++++- .../src/__tests__/dispatchEvent.test.js | 41 +++++++++++++++++++ .../x-live-blog-wrapper/src/dispatchEvent.js | 34 +++++++++++++++ 5 files changed, 94 insertions(+), 34 deletions(-) create mode 100644 components/x-live-blog-wrapper/src/__tests__/dispatchEvent.test.js create mode 100644 components/x-live-blog-wrapper/src/dispatchEvent.js diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 5a5be14db..19bb7895c 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -3,14 +3,16 @@ import { LiveBlogPost } from '@financial-times/x-live-blog-post' import { withActions } from '@financial-times/x-interaction' import { listenToLiveBlogEvents } from './LiveEventListener' import { normalisePost } from './normalisePost' +import { dispatchEvent } from './dispatchEvent' const withLiveBlogWrapperActions = withActions({ - insertPost(newPost) { + insertPost(newPost, wrapper) { return (props) => { const normalisedNewPost = normalisePost(newPost) const newPostAlreadyExists = props.posts.find((post) => post.id === normalisedNewPost.id) if (!newPostAlreadyExists) { props.posts.unshift(normalisedNewPost) + dispatchEvent(wrapper, 'LiveBlogWrapper.INSERT_POST', { post: normalisedNewPost }) } return props diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index 56ea2e438..09d78d25f 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -40,36 +40,6 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, } } - const dispatchLiveUpdateEvent = (eventType, data) => { - /* - We dispatch live update events to notify the consuming app about added / updated posts. - - Consuming app uses these events to execute tasks like initialising Origami components - on the updated elements. - - We want the rendering of the updates in the DOM to finish before dispatching this event, - because the consumer needs to reference the updated DOM elements. - - If we dispatch the event in the same event loop with DOM element updates, consumer app - will handle the event before the updates are complete. - - window.setTimeout(fn, 0) will defer the execution of the inner function until the - current event loop completes, which is enough time for the DOM updates to finish. - - More information can be found in MDN setTimeout documentation. Please refer to - "Late timeouts" heading in this page: - https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Late_timeouts - - > ... the timeout can also fire later when the page (or the OS/browser itself) is busy - > with other tasks. One important case to note is that the function or code snippet - > cannot be executed until the thread that called setTimeout() has terminated. - > ... - > This is because even though setTimeout was called with a delay of zero, it's placed on - > a queue and scheduled to run at the next opportunity; not immediately. - */ - window.setTimeout(() => wrapper.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0) - } - // Allow `next-live-event-api` endpoint URL to be set in development. const baseUrl = typeof LIVE_EVENT_API_URL !== 'undefined' ? LIVE_EVENT_API_URL : 'https://next-live-event.ft.com' // eslint-disable-line no-undef @@ -85,8 +55,7 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, return } - invokeAction('insertPost', [post]) - dispatchLiveUpdateEvent('LiveBlogWrapper.INSERT_POST', { post }) + invokeAction('insertPost', [post, wrapper]) }) } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 218deb17f..9557f849c 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -3,6 +3,9 @@ const { mount } = require('@financial-times/x-test-utils/enzyme') import { LiveBlogWrapper } from '../LiveBlogWrapper' +import { dispatchEvent } from '../dispatchEvent' +jest.mock('../dispatchEvent') + const post1 = { id: '1', title: 'Post 1 Title', @@ -61,17 +64,26 @@ describe('liveBlogWrapperActions', () => { actions = liveBlogWrapper.props.actions }) + afterEach(() => { + dispatchEvent.mockReset() + }) + it('inserts a new post to the top of the list', () => { + const target = 'target' const post3 = { id: '3' } // insertPost function returns another function that takes the list of component props // as an argument and returns the updated props. - actions.insertPost(post3)({ posts }) + actions.insertPost(post3, target)({ posts }) expect(posts.length).toEqual(3) expect(posts[0].id).toEqual('3') + + expect(dispatchEvent).toHaveBeenCalledWith('target', 'LiveBlogWrapper.INSERT_POST', { + post: post3 + }) }) it('does not insert a new post if a duplicate', () => { @@ -84,5 +96,7 @@ describe('liveBlogWrapperActions', () => { expect(posts.length).toEqual(2) expect(posts[0].id).toEqual('1') + + expect(dispatchEvent).not.toHaveBeenCalled() }) }) diff --git a/components/x-live-blog-wrapper/src/__tests__/dispatchEvent.test.js b/components/x-live-blog-wrapper/src/__tests__/dispatchEvent.test.js new file mode 100644 index 000000000..02617a205 --- /dev/null +++ b/components/x-live-blog-wrapper/src/__tests__/dispatchEvent.test.js @@ -0,0 +1,41 @@ +import { dispatchEvent } from '../dispatchEvent' + +const CustomEventMock = jest.fn() +global.CustomEvent = CustomEventMock + +const dispatchEventMock = jest.fn(() => {}) + +describe('dispatchEvent', () => { + beforeAll(() => { + jest.useFakeTimers() + }) + afterAll(() => { + dispatchEventMock.mockReset() + CustomEventMock.mockReset() + jest.useRealTimers() + }) + it('does nothing if ran on the server with no window', () => { + const originalWindow = global.window + delete global.window + dispatchEvent() + jest.runAllTimers() + expect(dispatchEventMock).not.toHaveBeenCalled() + expect(CustomEventMock).not.toHaveBeenCalled() + global.window = originalWindow + }) + it('does loads if window', () => { + const target = { + dispatchEvent: dispatchEventMock + } + dispatchEvent(target, 'test', { + foo: 'bar' + }) + jest.runAllTimers() + expect(dispatchEventMock).toHaveBeenCalled() + expect(CustomEventMock).toHaveBeenCalledWith('test', { + detail: { + foo: 'bar' + } + }) + }) +}) diff --git a/components/x-live-blog-wrapper/src/dispatchEvent.js b/components/x-live-blog-wrapper/src/dispatchEvent.js new file mode 100644 index 000000000..6c55ffdc3 --- /dev/null +++ b/components/x-live-blog-wrapper/src/dispatchEvent.js @@ -0,0 +1,34 @@ +/* +We dispatch live update events to notify the consuming app about added / updated posts. + +Consuming app uses these events to execute tasks like initialising Origami components +on the updated elements. + +We want the rendering of the updates in the DOM to finish before dispatching this event, +because the consumer needs to reference the updated DOM elements. + +If we dispatch the event in the same event loop with DOM element updates, consumer app +will handle the event before the updates are complete. + +window.setTimeout(fn, 0) will defer the execution of the inner function until the +current event loop completes, which is enough time for the DOM updates to finish. + +More information can be found in MDN setTimeout documentation. Please refer to +"Late timeouts" heading in this page: +https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Late_timeouts + +> ... the timeout can also fire later when the page (or the OS/browser itself) is busy +> with other tasks. One important case to note is that the function or code snippet +> cannot be executed until the thread that called setTimeout() has terminated. +> ... +> This is because even though setTimeout was called with a delay of zero, it's placed on +> a queue and scheduled to run at the next opportunity; not immediately. +*/ +const dispatchEvent = (target, eventType, data) => { + if (typeof window === 'undefined') { + return + } + window.setTimeout(() => target.dispatchEvent(new CustomEvent(eventType, { detail: data })), 0) +} + +export { dispatchEvent } From 824129b470bb29f870fd97bd48ca510111c505d8 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Thu, 21 Jan 2021 15:16:12 +0000 Subject: [PATCH 667/760] Add test coverage for LiveEventListener file --- .../src/__tests__/LiveEventListener.test.js | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 components/x-live-blog-wrapper/src/__tests__/LiveEventListener.test.js diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveEventListener.test.js b/components/x-live-blog-wrapper/src/__tests__/LiveEventListener.test.js new file mode 100644 index 000000000..18416d58f --- /dev/null +++ b/components/x-live-blog-wrapper/src/__tests__/LiveEventListener.test.js @@ -0,0 +1,90 @@ +import { listenToLiveBlogEvents } from '../LiveEventListener' + +const addEventListenerMock = jest.fn() +const EventSourceMock = jest.fn(() => { + return { + addEventListener: addEventListenerMock + } +}) +global.EventSource = EventSourceMock + +const dispatchEventMock = jest.fn() +jest.spyOn(document, 'querySelector').mockImplementation(() => { + return { + dispatchEvent: dispatchEventMock + } +}) + +describe('liveEventListener', () => { + describe('EventSource', () => { + it('should default to the live URL', () => { + listenToLiveBlogEvents({}) + expect(EventSourceMock).toHaveBeenCalledWith('https://next-live-event.ft.com/v2/liveblog/undefined', { + withCredentials: true + }) + }) + + it('should allow baseUrl to be overriden for testing', () => { + global.LIVE_EVENT_API_URL = 'http://localhost:5000' + listenToLiveBlogEvents({}) + expect(EventSourceMock).toHaveBeenCalledWith('http://localhost:5000/v2/liveblog/undefined', { + withCredentials: true + }) + delete global.LIVE_EVENT_API_URL + }) + + it('should use the liveBlogPackageUuid as a source', () => { + listenToLiveBlogEvents({ liveBlogPackageUuid: 1234 }) + expect(EventSourceMock).toHaveBeenCalledWith('https://next-live-event.ft.com/v2/liveblog/1234', { + withCredentials: true + }) + }) + }) + + describe('listening to events', () => { + afterAll(() => { + addEventListenerMock.mockReset() + dispatchEventMock.mockReset() + }) + it('throws exception if no stringified data', () => { + addEventListenerMock.mockImplementation((event, handler) => { + handler({}) + }) + expect(() => { + listenToLiveBlogEvents({}) + }).toThrow('Unexpected token') + }) + it('if actions are supplied, they should be called', () => { + addEventListenerMock.mockImplementation((event, handler) => { + handler({ + data: JSON.stringify({ + id: 1234 + }) + }) + }) + listenToLiveBlogEvents({ + actions: { + insertPost: (event, wrapper) => { + expect(wrapper).toEqual({ + dispatchEvent: dispatchEventMock + }) + expect(event).toEqual({ + id: 1234 + }) + } + } + }) + }) + it('if no actions supplied, then an event should be dispatched', () => { + addEventListenerMock.mockImplementation((event, handler) => { + handler({ + data: JSON.stringify({ + id: 1234 + }) + }) + }) + listenToLiveBlogEvents({}) + expect(dispatchEventMock).toHaveBeenCalled() + }) + }) +}) From 770210f3511cbecf35d79070292d184a4bd86ae2 Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Fri, 22 Jan 2021 14:58:11 +0000 Subject: [PATCH 668/760] Throw error if hydrating on unregistered component --- components/x-interaction/src/Hydrate.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/x-interaction/src/Hydrate.jsx b/components/x-interaction/src/Hydrate.jsx index 064b9b455..2eb73d52a 100644 --- a/components/x-interaction/src/Hydrate.jsx +++ b/components/x-interaction/src/Hydrate.jsx @@ -52,6 +52,12 @@ export function hydrate() { const Component = getComponent(component) + if (!Component) { + throw new Error( + `x-interaction hydrate was called using unregistered component: ${Component}. please verify you'are registering your component using x-interaction's registerComponent function before attempting to hydrate.` + ) + } + while (wrapper.firstChild) { wrapper.removeChild(wrapper.firstChild) } From 47447e8ef1a3889f0c5e6485c0cebc7cca5c42ea Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Fri, 22 Jan 2021 14:59:18 +0000 Subject: [PATCH 669/760] remove automatic registration from withActions --- components/x-interaction/src/Interaction.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/x-interaction/src/Interaction.jsx b/components/x-interaction/src/Interaction.jsx index 033a5d430..fe83deb50 100644 --- a/components/x-interaction/src/Interaction.jsx +++ b/components/x-interaction/src/Interaction.jsx @@ -2,7 +2,6 @@ import { h } from '@financial-times/x-engine' import { InteractionClass } from './InteractionClass' import { InteractionSSR } from './InteractionSSR' import wrapComponentName from './concerns/wrap-component-name' -import { registerComponent } from './concerns/register-component' // use the class version for clientside and the static version for server const Interaction = typeof window !== 'undefined' ? InteractionClass : InteractionSSR @@ -55,12 +54,10 @@ export const withActions = (getActions, getDefaultState = {}) => (Component) => // set the displayName of the Enhanced component for debugging wrapComponentName(Component, Enhanced) - // register the component under its name for later hydration from serialised data - registerComponent(Enhanced) - return Enhanced } export { hydrate } from './Hydrate' export { HydrationData } from './HydrationData' export { Serialiser } from './concerns/serialiser' +export { registerComponent } from './concerns/register-component' From bfd919e953d0716e6c9125256c33adc450fe55be Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Fri, 22 Jan 2021 15:01:06 +0000 Subject: [PATCH 670/760] remove reliance on component displayName in registerComponent --- .../x-interaction/src/concerns/register-component.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/components/x-interaction/src/concerns/register-component.js b/components/x-interaction/src/concerns/register-component.js index f09653030..6a245e37a 100644 --- a/components/x-interaction/src/concerns/register-component.js +++ b/components/x-interaction/src/concerns/register-component.js @@ -1,9 +1,15 @@ const registeredComponents = {} -export function registerComponent(component) { - registeredComponents[component.wrappedDisplayName] = component +export function registerComponent(component, name) { + const xInteractionName = Symbol('x-interaction-name') + component[xInteractionName] = name + registeredComponents[name] = component } -export function getComponent(name) { +export function getComponent(component) { + const xInteractionSymbol = Object.getOwnPropertySymbols(component).find( + (key) => key.toString() === `Symbol(x-interaction-name)` + ) + const name = component[xInteractionSymbol] return registeredComponents[name] } From 63405e206aad76ca8396401e4704b250a288db40 Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Fri, 22 Jan 2021 15:09:44 +0000 Subject: [PATCH 671/760] throw error if using Serialiser from unregistered component --- components/x-interaction/src/concerns/serialiser.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/x-interaction/src/concerns/serialiser.js b/components/x-interaction/src/concerns/serialiser.js index 6e252623a..a2d4d6287 100644 --- a/components/x-interaction/src/concerns/serialiser.js +++ b/components/x-interaction/src/concerns/serialiser.js @@ -1,6 +1,7 @@ import { h, render } from '@financial-times/x-engine' import getComponentName from './get-component-name' import { HydrationData } from '../HydrationData' +import { getComponent } from './register-component' export class Serialiser { constructor() { @@ -9,6 +10,14 @@ export class Serialiser { } addData({ id, Component, props }) { + const registeredComponent = getComponent(Component) + + if (!registeredComponent) { + throw new Error( + `a Serialiser's addData was called for an unregistered component. ensure you're registering your component before attempting to output the hydration data` + ) + } + if (this.destroyed) { throw new Error( `an interaction component was rendered after flushHydrationData was called. ensure you're outputting the hydration data after rendering every component` From a382ac792c1299fbdfbfd57c4741d2451c82e77c Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Fri, 22 Jan 2021 15:10:24 +0000 Subject: [PATCH 672/760] update LiveBlogWrapper to use registerComponent pick a5bb8096 update LiveBlogWrapper to use registerComponent --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 19bb7895c..549d1c5ce 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -4,6 +4,7 @@ import { withActions } from '@financial-times/x-interaction' import { listenToLiveBlogEvents } from './LiveEventListener' import { normalisePost } from './normalisePost' import { dispatchEvent } from './dispatchEvent' +import { registerComponent } from '@financial-times/x-interaction' const withLiveBlogWrapperActions = withActions({ insertPost(newPost, wrapper) { @@ -53,9 +54,8 @@ const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id, liv ) } -// A displayName is required for this component // This enables the component to work with x-interaction -BaseLiveBlogWrapper.displayName = 'BaseLiveBlogWrapper' +registerComponent(BaseLiveBlogWrapper, 'BaseLiveBlogWrapper') const LiveBlogWrapper = withLiveBlogWrapperActions(BaseLiveBlogWrapper) From 91d949b2bff7258dd65494f449519e4ba8b36165 Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Fri, 22 Jan 2021 15:12:23 +0000 Subject: [PATCH 673/760] add registerComponent step to readme --- components/x-interaction/readme.md | 36 ++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/components/x-interaction/readme.md b/components/x-interaction/readme.md index 94dcfe56c..f29ec140f 100644 --- a/components/x-interaction/readme.md +++ b/components/x-interaction/readme.md @@ -113,7 +113,35 @@ export const Greeting = greetingActions(BaseGreeting); When you have an `x-interaction` component rendered by the server, and you want to attach the client-side version of the component to handle the actions, rather than rendering the component manually (which might become unwieldy, especially if you have many components & instances on the page), you can have `x-interaction` manage it for you. -There are two parts to this: serialising and hydrating. +There are three parts to this: registering the component, serialising and hydrating. + +#### Registering the component + +To register the component you'll need to call `x-interaction`'s `registerComponent` function, providing the component and its name as arguments. + +```jsx +import {withActions, registerComponent} from '@financial-times/x-interaction'; + +const greetingActions = withActions({ + actionOne() { + return {greeting: "world"}; + }, + + actionTwo() { + return ({greeting}) => ({ + greeting: greeting.toUpperCase(), + }); + }, +}); + +const Greeting = greetingActions(({greeting, actions}) => <div> + hello {greeting} + <button onClick={actions.actionOne}>"world"</button> + <button onClick={actions.actionTwo}>uppercase</button> +</div>); + +registerComponent(Greeting, 'Greeting') +``` #### Serialising @@ -123,12 +151,12 @@ This instance should be passed to every `x-interaction` component you render, as Finally, after every `x-interaction` component is rendered, you should output the hydration data. `x-interaction` exports a `HydrationData` component, which takes a serialiser as a property and renders a `<script>` tag containing its hydration data, assigned to a global variable that can be picked up by the `x-interaction` client-side runtime. A serialiser cannot be used again after its data has been output by a `HydrationData` component. -Here's a full example of using `Serialiser` and `HydrationData`: +Here's a full example of using `Serialiser` and `HydrationData` using the `Greeting` component we registered in the previous step. ```js import express from 'express'; +import { Greeting } from './Greeting' import { Serialiser, HydrationData } from '@financial-times/x-interaction'; -import { Increment } from '@financial-times/x-increment'; const app = express(); @@ -136,7 +164,7 @@ app.get('/', (req, res) => { const serialiser = new Serialiser(); res.send(` - ${Increment({ count: 1, serialiser })} + ${Greeting({ serialiser })} ${HydrationData({ serialiser })} `); }); From 2900113b3855cd8c68fc073816494127bc078916 Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Tue, 26 Jan 2021 16:39:11 +0000 Subject: [PATCH 674/760] added fixes for registering components --- components/x-interaction/src/Hydrate.jsx | 6 ++-- .../x-interaction/src/InteractionSSR.jsx | 2 +- .../src/concerns/get-component-name.js | 3 -- .../src/concerns/register-component.js | 28 +++++++++++++------ .../x-interaction/src/concerns/serialiser.js | 3 +- .../src/concerns/wrap-component-name.js | 2 +- 6 files changed, 25 insertions(+), 19 deletions(-) delete mode 100644 components/x-interaction/src/concerns/get-component-name.js diff --git a/components/x-interaction/src/Hydrate.jsx b/components/x-interaction/src/Hydrate.jsx index 2eb73d52a..6367c4ab6 100644 --- a/components/x-interaction/src/Hydrate.jsx +++ b/components/x-interaction/src/Hydrate.jsx @@ -1,5 +1,5 @@ import { h, render, Component } from '@financial-times/x-engine' -import { getComponent } from './concerns/register-component' +import { getComponentByName } from './concerns/register-component' export class HydrationWrapper extends Component { render() { @@ -50,11 +50,11 @@ export function hydrate() { ) } - const Component = getComponent(component) + const Component = getComponentByName(component) if (!Component) { throw new Error( - `x-interaction hydrate was called using unregistered component: ${Component}. please verify you'are registering your component using x-interaction's registerComponent function before attempting to hydrate.` + `x-interaction hydrate was called using unregistered component: ${component}. please verify you're registering your component using x-interaction's registerComponent function before attempting to hydrate.` ) } diff --git a/components/x-interaction/src/InteractionSSR.jsx b/components/x-interaction/src/InteractionSSR.jsx index b7681140c..747491eec 100644 --- a/components/x-interaction/src/InteractionSSR.jsx +++ b/components/x-interaction/src/InteractionSSR.jsx @@ -1,5 +1,5 @@ import { h } from '@financial-times/x-engine' -import getComponentName from './concerns/get-component-name' +import { getComponentName } from './concerns/register-component' import shortId from '@quarterto/short-id' import { InteractionRender } from './InteractionRender' diff --git a/components/x-interaction/src/concerns/get-component-name.js b/components/x-interaction/src/concerns/get-component-name.js deleted file mode 100644 index bd14d40c3..000000000 --- a/components/x-interaction/src/concerns/get-component-name.js +++ /dev/null @@ -1,3 +0,0 @@ -const getComponentName = (Component) => Component.displayName || Component.name || 'Unknown' - -export default getComponentName diff --git a/components/x-interaction/src/concerns/register-component.js b/components/x-interaction/src/concerns/register-component.js index 6a245e37a..352c28a05 100644 --- a/components/x-interaction/src/concerns/register-component.js +++ b/components/x-interaction/src/concerns/register-component.js @@ -1,15 +1,25 @@ const registeredComponents = {} +const xInteractionName = Symbol('x-interaction-name') -export function registerComponent(component, name) { - const xInteractionName = Symbol('x-interaction-name') - component[xInteractionName] = name - registeredComponents[name] = component +export function registerComponent(Component, name) { + if (registeredComponents[name]) { + throw new Error( + `x-interaction a component has already been registered under that name, please use another name.` + ) + } + Component[xInteractionName] = name + registeredComponents[name] = Component } -export function getComponent(component) { - const xInteractionSymbol = Object.getOwnPropertySymbols(component).find( - (key) => key.toString() === `Symbol(x-interaction-name)` - ) - const name = component[xInteractionSymbol] +export function getComponent(Component) { + const name = Component[xInteractionName] return registeredComponents[name] } + +export function getComponentByName(name) { + return registeredComponents[name] +} + +export function getComponentName(Component) { + return Component[xInteractionName] || 'Unknown' +} diff --git a/components/x-interaction/src/concerns/serialiser.js b/components/x-interaction/src/concerns/serialiser.js index a2d4d6287..0ce0d99b1 100644 --- a/components/x-interaction/src/concerns/serialiser.js +++ b/components/x-interaction/src/concerns/serialiser.js @@ -1,7 +1,6 @@ import { h, render } from '@financial-times/x-engine' -import getComponentName from './get-component-name' import { HydrationData } from '../HydrationData' -import { getComponent } from './register-component' +import { getComponent, getComponentName } from './register-component' export class Serialiser { constructor() { diff --git a/components/x-interaction/src/concerns/wrap-component-name.js b/components/x-interaction/src/concerns/wrap-component-name.js index db841f64f..ca8bee763 100644 --- a/components/x-interaction/src/concerns/wrap-component-name.js +++ b/components/x-interaction/src/concerns/wrap-component-name.js @@ -1,4 +1,4 @@ -import getComponentName from './get-component-name' +import { getComponentName } from './register-component' function wrapComponentName(Component, Enhanced) { const originalDisplayName = getComponentName(Component) From 1e4bc564c8090d1a2d30f7616fda70f413c810ea Mon Sep 17 00:00:00 2001 From: AniaMakes <anna.bebb@ft.com> Date: Tue, 26 Jan 2021 17:13:07 +0000 Subject: [PATCH 675/760] gift pop up improvements wrapped in a flag --- .../x-gift-article/src/CopyConfirmation.jsx | 12 ++++--- components/x-gift-article/src/Form.jsx | 8 +++-- components/x-gift-article/src/GiftArticle.jsx | 5 +++ .../x-gift-article/src/GiftArticle.scss | 10 +++++- components/x-gift-article/src/Message.jsx | 35 ++++++++++++++++++- .../src/RadioButtonsSection.jsx | 26 ++++++++++---- components/x-gift-article/src/Title.jsx | 33 ++++++++++++++--- components/x-gift-article/src/UrlSection.jsx | 4 ++- components/x-gift-article/src/lib/updaters.js | 2 +- 9 files changed, 113 insertions(+), 22 deletions(-) diff --git a/components/x-gift-article/src/CopyConfirmation.jsx b/components/x-gift-article/src/CopyConfirmation.jsx index ef6ad59db..787e9ec24 100644 --- a/components/x-gift-article/src/CopyConfirmation.jsx +++ b/components/x-gift-article/src/CopyConfirmation.jsx @@ -9,14 +9,18 @@ const confirmationClassNames = [ styles['copy-confirmation'] ].join(' ') -export default ({ hideCopyConfirmation }) => ( +export default ({ hideCopyConfirmation, isTestVariant }) => ( <div className={confirmationClassNames} role="alert"> <div className={styles['o-message__container']}> <div className={styles['o-message__content']}> <p className={styles['o-message__content-main']}> - <span className={styles['o-message__content-highlight']}> - The link has been copied to your clipboard - </span> + {isTestVariant ? ( + <span>The link has been copied to your clipboard</span> + ) : ( + <span className={styles['o-message__content-highlight']}> + The link has been copied to your clipboard + </span> + )} </p> </div> diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index bc6fc2fa2..2322ee1e6 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -10,11 +10,12 @@ export default (props) => ( <div className={styles.container}> <form name="gift-form" className={styles['share-form']}> <div role="group" arialabelledby="gift-article-title"> - <Title title={props.title} /> + <Title {...props} /> {!props.isFreeArticle && ( <RadioButtonsSection shareType={props.shareType} + isArticleSharingUxUpdates={props.isArticleSharingUxUpdates} showGiftUrlSection={props.actions.showGiftUrlSection} showNonGiftUrlSection={props.actions.showNonGiftUrlSection} /> @@ -25,7 +26,10 @@ export default (props) => ( </form> {props.showCopyConfirmation && ( - <CopyConfirmation hideCopyConfirmation={props.actions.hideCopyConfirmation} /> + <CopyConfirmation + hideCopyConfirmation={props.actions.hideCopyConfirmation} + isTestVariant={props.isArticleSharingUxUpdates} + /> )} {props.showMobileShareLinks && <MobileShareButtons mobileShareLinks={props.mobileShareLinks} />} diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 04daec969..38c4232de 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -12,6 +12,9 @@ import * as updaters from './lib/updaters' const isCopySupported = typeof document !== 'undefined' && document.queryCommandSupported && document.queryCommandSupported('copy') +const todayDate = new Date() +const monthNow = `${updaters.monthNames[todayDate.getMonth()]}` + const withGiftFormActions = withActions( (initialProps) => { const api = new ApiClient({ @@ -122,10 +125,12 @@ const withGiftFormActions = withActions( title: 'Share this article', giftCredits: undefined, monthlyAllowance: undefined, + monthNow: monthNow, showCopyButton: isCopySupported, isGiftUrlCreated: false, isGiftUrlShortened: false, isNonGiftUrlShortened: false, + isArticleSharingUxUpdates: false, urls: { dummy: 'https://on.ft.com/gift_link', diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index 6e2180a52..e3a0f2d36 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -53,6 +53,14 @@ $o-typography-is-silent: true; .share-option-title { @include oNormaliseVisuallyHidden(); } + + .o-forms-field{ + @media only screen and (min-width: 600px) { + label[for$="Link"]{ + margin-bottom: 0; + } + } + } } @media only screen and (min-width: 600px) { @@ -130,4 +138,4 @@ $o-typography-is-silent: true; overflow: hidden; clip: rect(0,0,0,0); border: 0; -} \ No newline at end of file +} diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 7c51b39f5..156c75c1e 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -12,8 +12,41 @@ export default ({ monthlyAllowance, nextRenewalDateText, redemptionLimit, - invalidResponseFromApi + invalidResponseFromApi, + isArticleSharingUxUpdates }) => { + if (isArticleSharingUxUpdates) { + if (isFreeArticle) { + return null + } + + if (shareType === ShareType.gift) { + if (giftCredits === 0) { + return ( + <div className={messageClassName}> + You’ve used all your <strong>gift article credits</strong> + <br /> + You’ll get your next {monthlyAllowance} on <strong>{nextRenewalDateText}</strong> + </div> + ) + } + + if (invalidResponseFromApi) { + return <div className={messageClassName}>Unable to fetch gift credits. Please try again later</div> + } + + return ( + <div className={messageClassName}> + A gift link can be opened up to <strong>{redemptionLimit ? redemptionLimit : 3} times</strong> + </div> + ) + } + + if (shareType === ShareType.nonGift) { + return <div className={messageClassName}>This link can only be read by existing subscribers</div> + } + } + if (isFreeArticle) { return ( <div className={messageClassName}> diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index 9d61d1427..c94181e4b 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -10,7 +10,7 @@ const radioSectionClassNames = [ styles['radio-button-section'] ].join(' ') -export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( +export default ({ shareType, showGiftUrlSection, isArticleSharingUxUpdates, showNonGiftUrlSection }) => ( <div className={radioSectionClassNames} role="group" aria-labelledby="article-share-options"> <span className={styles['share-option-title']} id="article-share-options"> Article share options @@ -24,9 +24,15 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( checked={shareType === ShareType.gift} onChange={showGiftUrlSection} /> - <span className={styles['o-forms-input__label']}> - with <strong>anyone</strong> (uses 1 gift credit) - </span> + {isArticleSharingUxUpdates ? ( + <span className={styles['o-forms-input__label']}> + Gift to <strong>anyone</strong> (uses <strong>1 credit</strong>) + </span> + ) : ( + <span className={styles['o-forms-input__label']}> + with <strong>anyone</strong> (uses 1 gift credit) + </span> + )} </label> <label htmlFor="nonGiftLink"> @@ -38,9 +44,15 @@ export default ({ shareType, showGiftUrlSection, showNonGiftUrlSection }) => ( checked={shareType === ShareType.nonGift} onChange={showNonGiftUrlSection} /> - <span className={styles['o-forms-input__label']}> - with <strong>other FT subscribers</strong> - </span> + {isArticleSharingUxUpdates ? ( + <span className={styles['o-forms-input__label']}> + Share with <strong>other FT subscribers</strong> + </span> + ) : ( + <span className={styles['o-forms-input__label']}> + with <strong>other FT subscribers</strong> + </span> + )} </label> </div> ) diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx index f2b468025..8325177df 100644 --- a/components/x-gift-article/src/Title.jsx +++ b/components/x-gift-article/src/Title.jsx @@ -3,8 +3,31 @@ import styles from './GiftArticle.scss' const titleClassNames = [styles.title].join(' ') -export default ({ title }) => ( - <div className={titleClassNames} id="gift-article-title"> - {title} - </div> -) +export default ({ + giftCredits, + monthlyAllowance, + monthNow, + isFreeArticle, + isArticleSharingUxUpdates, + title = '' +}) => { + if (isArticleSharingUxUpdates) { + if (title !== 'Share on Social') { + if (isFreeArticle) { + title = 'This article is free for anyone to read' + } else { + title = `You have ${giftCredits} out of ${monthlyAllowance} gift credits left in ${monthNow}` + } + } + + return ( + <div className={titleClassNames} id="gift-article-title"> + {title} + </div> + ) + } else { + ;<div className={titleClassNames} id="gift-article-title"> + {title} + </div> + } +} diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index 476999df3..b1d6e8547 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -21,6 +21,7 @@ export default ({ showCopyButton, nativeShare, invalidResponseFromApi, + isArticleSharingUxUpdates, actions }) => { const hideUrlShareElements = giftCredits === 0 && shareType === ShareType.gift @@ -51,7 +52,8 @@ export default ({ monthlyAllowance, nextRenewalDateText, redemptionLimit, - invalidResponseFromApi + invalidResponseFromApi, + isArticleSharingUxUpdates }} /> diff --git a/components/x-gift-article/src/lib/updaters.js b/components/x-gift-article/src/lib/updaters.js index 4975c2dcd..ea471c401 100644 --- a/components/x-gift-article/src/lib/updaters.js +++ b/components/x-gift-article/src/lib/updaters.js @@ -1,7 +1,7 @@ import { createMailtoUrl } from './share-link-actions' import { ShareType, UrlType } from './constants' -const monthNames = [ +export const monthNames = [ 'January', 'February', 'March', From 03cea57146f6fd8dc9027dc22516d40643fd5ddf Mon Sep 17 00:00:00 2001 From: andygout <andygout@hotmail.co.uk> Date: Wed, 27 Jan 2021 15:50:10 +0000 Subject: [PATCH 676/760] Update Releasing/Versioning link in contribution.md --- contribution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribution.md b/contribution.md index fbeec9021..43f7b296a 100644 --- a/contribution.md +++ b/contribution.md @@ -7,7 +7,7 @@ So you'd like to contribute some code, report a bug, or request a feature? You'r - [Opening a Pull Request](#opening-a-pull-request) - [Code Style](#code-style) - [Testing](#testing) - - [Releasing/Versioning](#releasingversioning) + - [Releasing/Versioning](/docs/components/release-guidelines.md#user-content-releasingversioning) ## Reporting bugs From 6a5df1ea104e3e58eb5b7a595f6d32764e03dde6 Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Fri, 5 Feb 2021 13:27:08 +0000 Subject: [PATCH 677/760] removed references to master and replace them with main or HEAD --- .circleci/config.yml | 6 +++--- .github/pull_request_template.md | 2 +- .github/settings.yml | 2 +- components/x-follow-button/readme.md | 4 ++-- components/x-gift-article/readme.md | 2 +- components/x-interaction/package.json | 2 +- components/x-live-blog-post/package.json | 2 +- components/x-live-blog-post/readme.md | 2 +- components/x-live-blog-wrapper/package.json | 2 +- components/x-live-blog-wrapper/readme.md | 2 +- components/x-live-blog-wrapper/src/LiveEventListener.js | 2 +- components/x-podcast-launchers/package.json | 2 +- components/x-podcast-launchers/readme.md | 2 +- components/x-privacy-manager/package.json | 2 +- components/x-privacy-manager/readme.md | 2 +- components/x-teaser-timeline/package.json | 2 +- components/x-teaser-timeline/readme.md | 4 ++-- components/x-teaser/package.json | 2 +- components/x-teaser/readme.md | 2 +- contribution.md | 6 +++--- docs/components/overview.md | 2 +- docs/components/release-guidelines.md | 8 ++++---- docs/components/stories.md | 2 +- docs/components/testing.md | 2 +- docs/get-started/what-is-x-dash.md | 2 +- docs/get-started/working-with-x-dash.md | 2 +- packages/x-engine/package.json | 2 +- packages/x-handlebars/package.json | 2 +- packages/x-handlebars/readme.md | 2 +- packages/x-node-jsx/package.json | 2 +- private/blueprints/component/package.json | 2 +- private/blueprints/component/readme.md | 2 +- readme.md | 4 ++-- 33 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dad3d93c6..e1269872c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -82,10 +82,10 @@ references: branches: ignore: /.*/ - filters_only_master: &filters_only_master + filters_only_main: &filters_only_main branches: only: - - master + - main jobs: @@ -188,7 +188,7 @@ workflows: - build - deploy: filters: - <<: *filters_only_master + <<: *filters_only_main requires: - test diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3e1a666f3..9e20308da 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -If this is your first `x-dash` pull request please familiarise yourself with the [contribution guide](https://github.com/Financial-Times/x-dash/blob/master/contribution.md) before submitting. +If this is your first `x-dash` pull request please familiarise yourself with the [contribution guide](https://github.com/Financial-Times/x-dash/blob/HEAD/contribution.md) before submitting. ## If you're creating a component: diff --git a/.github/settings.yml b/.github/settings.yml index 7e6111e82..8c831e50d 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -1,6 +1,6 @@ _extends: github-apps-config-next branches: - - name: master + - name: main protection: required_pull_request_reviews: required_approving_review_count: 1 diff --git a/components/x-follow-button/readme.md b/components/x-follow-button/readme.md index e1ce947ff..838f36ecf 100644 --- a/components/x-follow-button/readme.md +++ b/components/x-follow-button/readme.md @@ -1,6 +1,6 @@ # x-follow-button -This module provides a template for myFT follow topic button, and is intended to replace the legacy handlebars component in [n-myft-ui](https://github.com/Financial-Times/n-myft-ui/tree/master/components/follow-button). +This module provides a template for myFT follow topic button, and is intended to replace the legacy handlebars component in [n-myft-ui](https://github.com/Financial-Times/n-myft-ui/tree/HEAD/components/follow-button). ## Installation @@ -40,4 +40,4 @@ Property | Value It is up to the consumer of this component to listen for the `x-follow-button` event, and use this data, along with the user's ID, and carry out the appropriate action. For example, if using `next-myft-client` to carry out the follow/unfollow action, n-myft-ui provides a x-button-interaction component for this: -https://github.com/Financial-Times/n-myft-ui/blob/master/components/x-button-integration/index.js +https://github.com/Financial-Times/n-myft-ui/blob/HEAD/components/x-button-integration/index.js diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index dc22805a8..655ca96a3 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -65,7 +65,7 @@ All `x-` components are designed to be compatible with a variety of runtimes, no [jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ [interaction]: /components/x-interaction#triggering-actions-externally -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ### Properties diff --git a/components/x-interaction/package.json b/components/x-interaction/package.json index 896651938..f5c4a75fd 100644 --- a/components/x-interaction/package.json +++ b/components/x-interaction/package.json @@ -27,7 +27,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-interaction", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-interaction", "engines": { "node": "12.x" }, diff --git a/components/x-live-blog-post/package.json b/components/x-live-blog-post/package.json index ea4e01578..e21693b5c 100644 --- a/components/x-live-blog-post/package.json +++ b/components/x-live-blog-post/package.json @@ -29,7 +29,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-live-blog-post", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-live-blog-post", "engines": { "node": "12.x" }, diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index 181febec5..20f5fabcc 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -13,7 +13,7 @@ npm install --save @financial-times/x-live-blog-post The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ## Usage diff --git a/components/x-live-blog-wrapper/package.json b/components/x-live-blog-wrapper/package.json index e2dbca31e..e060fa08a 100644 --- a/components/x-live-blog-wrapper/package.json +++ b/components/x-live-blog-wrapper/package.json @@ -27,7 +27,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-live-blog-wrapper", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-live-blog-wrapper", "engines": { "node": "12.x" }, diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index 537b40a76..d42472e97 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -13,7 +13,7 @@ npm install --save @financial-times/x-live-blog-wrapper The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ## Usage diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js index 09d78d25f..9c3343040 100644 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ b/components/x-live-blog-wrapper/src/LiveEventListener.js @@ -24,7 +24,7 @@ const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, // argument is defined. // // For more information: - // https://github.com/Financial-Times/x-dash/tree/master/components/x-interaction#triggering-actions-externally + // https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-interaction#triggering-actions-externally actions[action](...args) } else { // When the component is rendered at the server side, we don't have a reference to diff --git a/components/x-podcast-launchers/package.json b/components/x-podcast-launchers/package.json index b7ddc4f35..90c3cf84e 100644 --- a/components/x-podcast-launchers/package.json +++ b/components/x-podcast-launchers/package.json @@ -31,7 +31,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-podcastlaunchers", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-podcastlaunchers", "engines": { "node": "12.x" }, diff --git a/components/x-podcast-launchers/readme.md b/components/x-podcast-launchers/readme.md index 5e9f8f166..29ce03d08 100644 --- a/components/x-podcast-launchers/readme.md +++ b/components/x-podcast-launchers/readme.md @@ -19,7 +19,7 @@ npm install --save @financial-times/x-podcast-launchers The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ## Styling diff --git a/components/x-privacy-manager/package.json b/components/x-privacy-manager/package.json index d08d35369..4c382d606 100644 --- a/components/x-privacy-manager/package.json +++ b/components/x-privacy-manager/package.json @@ -16,7 +16,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-privacy-manager", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-privacy-manager", "engines": { "node": "12.x" }, diff --git a/components/x-privacy-manager/readme.md b/components/x-privacy-manager/readme.md index 9cb4efb95..cc1a81c8f 100644 --- a/components/x-privacy-manager/readme.md +++ b/components/x-privacy-manager/readme.md @@ -16,7 +16,7 @@ npm install --save @financial-times/x-privacy-manager The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ## Usage diff --git a/components/x-teaser-timeline/package.json b/components/x-teaser-timeline/package.json index 443846106..4df7346fc 100644 --- a/components/x-teaser-timeline/package.json +++ b/components/x-teaser-timeline/package.json @@ -33,7 +33,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-teaser-timeline", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-teaser-timeline", "engines": { "node": "12.x" }, diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md index ad4d09370..65c517508 100644 --- a/components/x-teaser-timeline/readme.md +++ b/components/x-teaser-timeline/readme.md @@ -13,7 +13,7 @@ npm install --save @financial-times/x-teaser-timeline The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ## Other dependencies @@ -27,7 +27,7 @@ $o-teaser-is-silent: true; @include oTeaser(('default', 'images', 'timestamp'), ('small')); ``` -See the [x-teaser](https://github.com/Financial-Times/x-dash/tree/master/components/x-teaser) documentation. +See the [x-teaser](https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-teaser) documentation. ## Usage diff --git a/components/x-teaser/package.json b/components/x-teaser/package.json index e64ccf06c..023a6e0b0 100644 --- a/components/x-teaser/package.json +++ b/components/x-teaser/package.json @@ -27,7 +27,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-teaser", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-teaser", "engines": { "node": "12.x" }, diff --git a/components/x-teaser/readme.md b/components/x-teaser/readme.md index 30d9cc843..0e2247012 100644 --- a/components/x-teaser/readme.md +++ b/components/x-teaser/readme.md @@ -13,7 +13,7 @@ bower install --save o-teaser The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ## Concepts diff --git a/contribution.md b/contribution.md index 43f7b296a..06df649ef 100644 --- a/contribution.md +++ b/contribution.md @@ -85,9 +85,9 @@ Please do! All of the code in `x-dash` is peer-reviewed by members of The App an This project follows a workflow designed around project releases. It is less strict than [Gitflow] but we encourage the separation of stable, development, and experimental branches in order to follow a scheduled release cycle. -- The `master` branch is for the current stable release. Bugfixes are merged into this branch. -- The `development` branch is for upcoming major or minor releases. This branch tracks `master` and new features are merged into it. -- Branches for new features should track and raise pull requests against the `development` branch or `master` branch if there are not any upcoming releases planned. +- The `main` branch is for the current stable release. Bugfixes are merged into this branch. +- The `development` branch is for upcoming major or minor releases. This branch tracks `main` and new features are merged into it. +- Branches for new features should track and raise pull requests against the `development` branch or `main` branch if there are not any upcoming releases planned. [Gitflow]: https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow diff --git a/docs/components/overview.md b/docs/components/overview.md index dcd41db11..f6752ff12 100644 --- a/docs/components/overview.md +++ b/docs/components/overview.md @@ -55,7 +55,7 @@ In addition it is encouraged to write unit tests for interactive or complex comp ## Publishing -All x-dash components and packages will be published on the [npm registry] under the `@financial-times` organisation. Components in the `master` or current `development` branches of x-dash will all be published concurrently with the same version number. Experimental components may be published with an unstable version number using a [prerelease tag]. +All x-dash components and packages will be published on the [npm registry] under the `@financial-times` organisation. Components in the `main` or current `development` branches of x-dash will all be published concurrently with the same version number. Experimental components may be published with an unstable version number using a [prerelease tag]. [npm registry]: https://www.npmjs.com/ [prerelease tag]: ./release-guidelines.md diff --git a/docs/components/release-guidelines.md b/docs/components/release-guidelines.md index eb370f8c4..5255139b2 100644 --- a/docs/components/release-guidelines.md +++ b/docs/components/release-guidelines.md @@ -2,9 +2,9 @@ ## Experimental features -Only stable, well tested components and packages may be present in the master or development branches. _Any publishable code in the master or development branches must have been tested in both The App and FT.com_. This is so we do not release unproven components with a stable version number. +Only stable, well tested components and packages may be present in the main or development branches. _Any publishable code in the main or development branches must have been tested in both The App and FT.com_. This is so we do not release unproven components with a stable version number. -To develop your component create a new feature branch including your module name, for example if you are building a new tabs component you would create a branch named `feature-x-tabs`. Your component will stay in this branch until it is ready to be merged into the next major or minor release so you are encouraged to merge from or rebase onto the latest development or master branch regularly. You are welcome to raise pull requests against your feature branch if you need to. +To develop your component create a new feature branch including your module name, for example if you are building a new tabs component you would create a branch named `feature-x-tabs`. Your component will stay in this branch until it is ready to be merged into the next major or minor release so you are encouraged to merge from or rebase onto the latest development or main branch regularly. You are welcome to raise pull requests against your feature branch if you need to. Because experimental modules will not be included in any stable releases we allow them to be published separately using a pre-1.0.0 version number. You are free to make as many prereleases as you need. To create a prerelease of your experimental module you must create a tag in the format `module-name-v0.x.x`, for example to release the tabs component you would create tag named `x-tabs-v0.0.1` for the latest commit in the `feature-x-tabs` branch. @@ -15,11 +15,11 @@ When your new module is considered stable raise a pull request against the curre ## Releasing/Versioning -All of our projects are versioned using [Semantic Versioning], you should familiarise yourself with this. The following guide will outline how to tag and release a new version of all projects, it assumes that all the code you wish to release is now on the `master` or main branch. +All of our projects are versioned using [Semantic Versioning], you should familiarise yourself with this. The following guide will outline how to tag and release a new version of all projects, it assumes that all the code you wish to release is now on the `main` branch. 1. **Review the commits since the last release**. You can find the last release in the git log, or by using the compare feature on GitHub. Make sure you've pulled all of the latest changes. - 2. **Decide on a version**. Work out whether this release is major, minor, or patch level. Major releases are generally planned out; if a breaking change has snuck into `master` without prior-planning it may be worth removing it or attempting to make it backwards-compatible. + 2. **Decide on a version**. Work out whether this release is major, minor, or patch level. Major releases are generally planned out; if a breaking change has snuck into `main` without prior-planning it may be worth removing it or attempting to make it backwards-compatible. 3. **Add a release**. Create a release using the GitHub UI (note there should be a "v" preceeding the version number). This will automatically kick off a new build and publish each package. diff --git a/docs/components/stories.md b/docs/components/stories.md index 305d7b35d..40097416b 100644 --- a/docs/components/stories.md +++ b/docs/components/stories.md @@ -99,4 +99,4 @@ module.exports = (data, createKnob) => { }; ``` -[Storybook knobs add-on]: https://github.com/storybooks/storybook/tree/master/addons/knobs +[Storybook knobs add-on]: https://github.com/storybooks/storybook/tree/HEAD/addons/knobs diff --git a/docs/components/testing.md b/docs/components/testing.md index 4eebeb5b1..1223454f8 100644 --- a/docs/components/testing.md +++ b/docs/components/testing.md @@ -10,7 +10,7 @@ Snapshot tests are useful for ensuring that you don't accidentally change the ma x-dash comes with Jest and Enzyme pre-configured, to help save boilerplate and setup when testing your component. -For an example of Jest and Enzyme in use, and standard patterns for writing tests for a component, see the [x-increment tests](https://github.com/Financial-Times/x-dash/tree/master/components/x-increment/__tests__). +For an example of Jest and Enzyme in use, and standard patterns for writing tests for a component, see the [x-increment tests](https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-increment/__tests__). ### Setup diff --git a/docs/get-started/what-is-x-dash.md b/docs/get-started/what-is-x-dash.md index 756745e7e..2ed81e2e4 100644 --- a/docs/get-started/what-is-x-dash.md +++ b/docs/get-started/what-is-x-dash.md @@ -29,7 +29,7 @@ With x-dash we have introduced a [monorepo] project structure, new [contribution [monorepo]: https://en.wikipedia.org/wiki/Monorepo -[contribution guidelines]: https://github.com/Financial-Times/x-dash/blob/master/contribution.md +[contribution guidelines]: https://github.com/Financial-Times/x-dash/blob/HEAD/contribution.md [release process]: ../components/release-guidelines.md diff --git a/docs/get-started/working-with-x-dash.md b/docs/get-started/working-with-x-dash.md index 9d73f26ea..2ed585005 100644 --- a/docs/get-started/working-with-x-dash.md +++ b/docs/get-started/working-with-x-dash.md @@ -69,4 +69,4 @@ The best way to ensure you stick to the x-dash code style is to make your work c [Prettier]: https://prettier.io/ [ESLint]: https://eslint.org/ -[contribution guide]: https://github.com/Financial-Times/x-dash/blob/master/contribution.md +[contribution guide]: https://github.com/Financial-Times/x-dash/blob/HEAD/contribution.md diff --git a/packages/x-engine/package.json b/packages/x-engine/package.json index 094647549..6f4923fa9 100644 --- a/packages/x-engine/package.json +++ b/packages/x-engine/package.json @@ -16,7 +16,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine", "engines": { "node": "12.x" }, diff --git a/packages/x-handlebars/package.json b/packages/x-handlebars/package.json index 1b191cacd..bdb7ae83e 100644 --- a/packages/x-handlebars/package.json +++ b/packages/x-handlebars/package.json @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/packages/x-handlebars", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-handlebars", "engines": { "node": "12.x" }, diff --git a/packages/x-handlebars/readme.md b/packages/x-handlebars/readme.md index f3383c2b4..c72186fdb 100644 --- a/packages/x-handlebars/readme.md +++ b/packages/x-handlebars/readme.md @@ -43,7 +43,7 @@ xHandlebars({ This module will install the [x-engine][x-engine] module as a dependency to perform the rendering of `x-` components. Please refer to the x-engine documentation to setup your application with `x-engine`. [n-ui]: https://github.com/Financial-Times/n-ui/ -[x-engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[x-engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ## Usage diff --git a/packages/x-node-jsx/package.json b/packages/x-node-jsx/package.json index b81ad34f2..b6ecee6ce 100644 --- a/packages/x-node-jsx/package.json +++ b/packages/x-node-jsx/package.json @@ -16,7 +16,7 @@ "type": "git", "url": "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/packages/x-node-jsx", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-node-jsx", "engines": { "node": "12.x" }, diff --git a/private/blueprints/component/package.json b/private/blueprints/component/package.json index 68d108966..217b8e2c8 100644 --- a/private/blueprints/component/package.json +++ b/private/blueprints/component/package.json @@ -22,7 +22,7 @@ "type" : "git", "url" : "https://github.com/Financial-Times/x-dash.git" }, - "homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/{{packageName}}", + "homepage": "https://github.com/Financial-Times/x-dash/tree/HEAD/components/{{packageName}}", "engines": { "node": "12.x" }, diff --git a/private/blueprints/component/readme.md b/private/blueprints/component/readme.md index 3fe9b7a91..ee7c68bf2 100644 --- a/private/blueprints/component/readme.md +++ b/private/blueprints/component/readme.md @@ -13,7 +13,7 @@ npm install --save @financial-times/{{packageName}} The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. -[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine +[engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine ## Usage diff --git a/readme.md b/readme.md index 4129d8074..13a861464 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ <h1 align="center"> <img src="https://user-images.githubusercontent.com/271645/38416861-1e6c6202-398e-11e8-907c-8c199a03988a.png" width="200" alt=""><br> x-dash - <a href="https://circleci.com/gh/Financial-Times/x-dash/tree/master"> - <img alt="Build Status" src="https://circleci.com/gh/Financial-Times/x-dash/tree/master.svg?style=svg"> + <a href="https://circleci.com/gh/Financial-Times/x-dash/tree/main"> + <img alt="Build Status" src="https://circleci.com/gh/Financial-Times/x-dash/tree/main.svg?style=svg"> </a> </h1> From a08451e2d19de573e5a244b233eb6cc4ffc3046c Mon Sep 17 00:00:00 2001 From: Kara Brightwell <kara@153.io> Date: Mon, 8 Feb 2021 16:53:11 +0000 Subject: [PATCH 678/760] ensure only wrapped components are registered --- .../x-interaction/src/concerns/register-component.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/x-interaction/src/concerns/register-component.js b/components/x-interaction/src/concerns/register-component.js index 352c28a05..fd37b1d79 100644 --- a/components/x-interaction/src/concerns/register-component.js +++ b/components/x-interaction/src/concerns/register-component.js @@ -7,6 +7,13 @@ export function registerComponent(Component, name) { `x-interaction a component has already been registered under that name, please use another name.` ) } + + if (!Component._wraps) { + throw new Error( + `only x-interaction wrapped components (i.e. the component returned from withActions) can be registered` + ) + } + Component[xInteractionName] = name registeredComponents[name] = Component } From 36ef9a822bd1839d9944755c6add03957ce88a7a Mon Sep 17 00:00:00 2001 From: Kara Brightwell <kara@153.io> Date: Mon, 8 Feb 2021 16:53:58 +0000 Subject: [PATCH 679/760] add name to original unwrapped component --- components/x-interaction/src/concerns/register-component.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/x-interaction/src/concerns/register-component.js b/components/x-interaction/src/concerns/register-component.js index fd37b1d79..047da3f97 100644 --- a/components/x-interaction/src/concerns/register-component.js +++ b/components/x-interaction/src/concerns/register-component.js @@ -15,6 +15,8 @@ export function registerComponent(Component, name) { } Component[xInteractionName] = name + // add name to original component so we can access the wrapper from the original + Component._wraps.Component[xInteractionName] = name registeredComponents[name] = Component } From 06d2014064fc747cb7fd6c9fec378f2d45d7d560 Mon Sep 17 00:00:00 2001 From: Kara Brightwell <kara@153.io> Date: Mon, 8 Feb 2021 16:55:09 +0000 Subject: [PATCH 680/760] register wrapped liveblogwrapper --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 549d1c5ce..4bf578df1 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -54,9 +54,9 @@ const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id, liv ) } -// This enables the component to work with x-interaction -registerComponent(BaseLiveBlogWrapper, 'BaseLiveBlogWrapper') - const LiveBlogWrapper = withLiveBlogWrapperActions(BaseLiveBlogWrapper) +// This enables the component to work with x-interaction hydration +registerComponent(LiveBlogWrapper, 'LiveBlogWrapper') + export { LiveBlogWrapper, listenToLiveBlogEvents } From e3901a93de2cc5104645cd4fea38efabb881c9ad Mon Sep 17 00:00:00 2001 From: AniaMakes <anna.bebb@ft.com> Date: Tue, 9 Feb 2021 12:30:30 +0000 Subject: [PATCH 681/760] updated prop name to be consistent across the repo --- components/x-gift-article/src/CopyConfirmation.jsx | 4 ++-- components/x-gift-article/src/Form.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/src/CopyConfirmation.jsx b/components/x-gift-article/src/CopyConfirmation.jsx index 787e9ec24..9b2256a6c 100644 --- a/components/x-gift-article/src/CopyConfirmation.jsx +++ b/components/x-gift-article/src/CopyConfirmation.jsx @@ -9,12 +9,12 @@ const confirmationClassNames = [ styles['copy-confirmation'] ].join(' ') -export default ({ hideCopyConfirmation, isTestVariant }) => ( +export default ({ hideCopyConfirmation, isArticleSharingUxUpdates }) => ( <div className={confirmationClassNames} role="alert"> <div className={styles['o-message__container']}> <div className={styles['o-message__content']}> <p className={styles['o-message__content-main']}> - {isTestVariant ? ( + {isArticleSharingUxUpdates ? ( <span>The link has been copied to your clipboard</span> ) : ( <span className={styles['o-message__content-highlight']}> diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 2322ee1e6..a2a8b515f 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -28,7 +28,7 @@ export default (props) => ( {props.showCopyConfirmation && ( <CopyConfirmation hideCopyConfirmation={props.actions.hideCopyConfirmation} - isTestVariant={props.isArticleSharingUxUpdates} + isArticleSharingUxUpdates={props.isArticleSharingUxUpdates} /> )} From 59a206ceb84dcff96be8e413417aec7e41f25141 Mon Sep 17 00:00:00 2001 From: Kara Brightwell <kara@153.io> Date: Tue, 9 Feb 2021 15:33:13 +0000 Subject: [PATCH 682/760] use actual displayname not registered name for wrapped name it's only for debugging purposes so doesn't matter --- components/x-interaction/src/concerns/wrap-component-name.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/x-interaction/src/concerns/wrap-component-name.js b/components/x-interaction/src/concerns/wrap-component-name.js index ca8bee763..88cdcc2d3 100644 --- a/components/x-interaction/src/concerns/wrap-component-name.js +++ b/components/x-interaction/src/concerns/wrap-component-name.js @@ -1,7 +1,5 @@ -import { getComponentName } from './register-component' - function wrapComponentName(Component, Enhanced) { - const originalDisplayName = getComponentName(Component) + const originalDisplayName = Component.displayName || Component.name Enhanced.displayName = `withActions(${originalDisplayName})` Enhanced.wrappedDisplayName = originalDisplayName } From 585f0c142a767ab85f3f1c9e8bb261890c60f92a Mon Sep 17 00:00:00 2001 From: AniaMakes <anna.bebb@ft.com> Date: Wed, 10 Feb 2021 12:16:16 +0000 Subject: [PATCH 683/760] added a comment about the AB testing to the readme for x-gift-article --- components/x-gift-article/readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index dc22805a8..e50f0bfd5 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -77,3 +77,6 @@ Property | Type | Required | Note `nativeShare` | Boolean | no | This is a property for App to display Native Sharing. `apiProtocol` | String | no | The protocol to use when making requests to the gift article and URL shortening services. Ignored if `apiDomain` is not set. `apiDomain` | String | no | The domain to use when making requests to the gift article and URL shortening services. + +### +`isArticleSharingUxUpdates` boolean has been added as part of ACC-749 to enable AB testing of the impact of minor UX improvements to x-gift-article. Once AB testing is done, and decision to keep / remove has been made, the changes made in https://github.com/Financial-Times/x-dash/pull/579 need to be ditched or baked in as default. From 6638209303ba0798110a5128d1d309a8ae0c4c67 Mon Sep 17 00:00:00 2001 From: AniaMakes <anna.bebb@ft.com> Date: Mon, 15 Feb 2021 16:43:38 +0000 Subject: [PATCH 684/760] removed a stray semicolon and wrapped the div inside a return --- components/x-gift-article/src/Title.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/x-gift-article/src/Title.jsx b/components/x-gift-article/src/Title.jsx index 8325177df..53dfafd24 100644 --- a/components/x-gift-article/src/Title.jsx +++ b/components/x-gift-article/src/Title.jsx @@ -26,8 +26,10 @@ export default ({ </div> ) } else { - ;<div className={titleClassNames} id="gift-article-title"> - {title} - </div> + return ( + <div className={titleClassNames} id="gift-article-title"> + {title} + </div> + ) } } From 0ee31462a027eb0ebbb16d700cc4a17b55880768 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Fri, 26 Feb 2021 15:05:09 +0000 Subject: [PATCH 685/760] Minimal fix: refactor targets properties alone --- packages/x-rollup/src/rollup-config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/x-rollup/src/rollup-config.js b/packages/x-rollup/src/rollup-config.js index 41cf9a6f8..f4be8e6b8 100644 --- a/packages/x-rollup/src/rollup-config.js +++ b/packages/x-rollup/src/rollup-config.js @@ -28,7 +28,7 @@ module.exports = ({ input, pkg }) => { plugins: [ babel( babelConfig({ - targets: [{ node: 12 }] + targets: { node: 12 } }) ), ...plugins @@ -46,7 +46,7 @@ module.exports = ({ input, pkg }) => { plugins: [ babel( babelConfig({ - targets: [{ node: 12 }] + targets: { node: 12 } }) ), ...plugins @@ -64,7 +64,7 @@ module.exports = ({ input, pkg }) => { plugins: [ babel( babelConfig({ - targets: [{ browsers: ['ie 11'] }] + targets: { ie: '11' } }) ), ...plugins From 51f7cce32fb10ceed7f21bed1548a13e8bc8a72a Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Tue, 2 Mar 2021 11:56:41 +0000 Subject: [PATCH 686/760] Quote target versions for consistency --- packages/x-rollup/src/rollup-config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/x-rollup/src/rollup-config.js b/packages/x-rollup/src/rollup-config.js index f4be8e6b8..b022b2685 100644 --- a/packages/x-rollup/src/rollup-config.js +++ b/packages/x-rollup/src/rollup-config.js @@ -28,7 +28,7 @@ module.exports = ({ input, pkg }) => { plugins: [ babel( babelConfig({ - targets: { node: 12 } + targets: { node: '12' } }) ), ...plugins @@ -46,7 +46,7 @@ module.exports = ({ input, pkg }) => { plugins: [ babel( babelConfig({ - targets: { node: 12 } + targets: { node: '12' } }) ), ...plugins From d34b841e4c8a55c862a1e6873006d1785e948e23 Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Tue, 2 Mar 2021 12:05:18 +0000 Subject: [PATCH 687/760] Fix up tests --- components/x-privacy-manager/src/actions.js | 4 +++- packages/x-babel-config/jest.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/x-privacy-manager/src/actions.js b/components/x-privacy-manager/src/actions.js index 8bdcd6f15..0335b25b8 100644 --- a/components/x-privacy-manager/src/actions.js +++ b/components/x-privacy-manager/src/actions.js @@ -13,6 +13,8 @@ function onConsentChange(consent) { * @returns {({ isLoading, consent }: { isLoading: boolean, consent: boolean }) => Promise<{_response: _Response}>} */ function sendConsent({ consentApiUrl, onConsentSavedCallbacks, consentSource, cookieDomain, fow }) { + let res + return async ({ isLoading, consent }) => { if (isLoading) return @@ -41,7 +43,7 @@ function sendConsent({ consentApiUrl, onConsentSavedCallbacks, consentSource, co } try { - const res = await fetch(consentApiUrl, { + res = await fetch(consentApiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/packages/x-babel-config/jest.js b/packages/x-babel-config/jest.js index 1b3475872..f5d8a2517 100644 --- a/packages/x-babel-config/jest.js +++ b/packages/x-babel-config/jest.js @@ -2,7 +2,7 @@ const getBabelConfig = require('./') const babelJest = require('babel-jest') const base = getBabelConfig({ - targets: [{ node: 'current' }], + targets: { node: 'current' }, modules: 'commonjs' }) From 6604cfecb4818f36f734ebdae15df204b4a59f03 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Mon, 1 Mar 2021 14:20:05 +0000 Subject: [PATCH 688/760] Use named colours from palettte --- components/x-live-blog-post/src/LiveBlogPost.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 5d28fc293..941410aae 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -4,9 +4,9 @@ .live-blog-post { overflow: auto; - border-bottom: 1px solid oColorsMix(black, paper, 20); + border-bottom: 1px solid oColorsByName('black-20'); margin-top: oSpacingByName('s8'); - color: oColorsMix(black, paper, 90); + color: oColorsByName('black-90'); padding-bottom: oSpacingByName('s8'); } @@ -22,7 +22,7 @@ .live-blog-post__byline { @include oTypographySans($scale: -1, $weight: 'semibold'); - color: oColorsMix(black, paper, 70); + color: oColorsByName('black-70'); margin-top: oSpacingByName('s1'); margin-bottom: 20px; } @@ -43,7 +43,7 @@ .live-blog-post__timestamp-exact-time { @include oTypographySans($scale: -1, $weight: 'light'); - color: oColorsMix(black, paper, 60); + color: oColorsByName('black-60'); padding-left: oSpacingByName('s2'); } @@ -51,7 +51,7 @@ content: ''; display: block; width: oSpacingByName('s4'); - border-bottom: 4px solid oColorsMix(black, paper, 90); + border-bottom: 4px solid oColorsByName('black-90'); padding-top: oSpacingByName('s1'); } From c43b23d4185e538c613d829523e33117e64d1329 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Mon, 1 Mar 2021 14:20:18 +0000 Subject: [PATCH 689/760] Load fonts into storybook demo --- components/x-live-blog-post/storybook/index.jsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index ffd09a425..f17f6e759 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -1,5 +1,10 @@ import React from 'react' import { LiveBlogPost } from '../src/LiveBlogPost' +import BuildService from '../../../.storybook/build-service' + +const dependencies = { + 'o-typography': '^6.0.0' +} export default { title: 'x-live-blog-post', @@ -9,7 +14,12 @@ export default { } export const ContentBody = (args) => { - return <LiveBlogPost {...args} /> + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <LiveBlogPost {...args} /> + </div> + ) } ContentBody.args = { From ff136aedd2b5c570c0090faadcf6965127697228 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Mon, 1 Mar 2021 14:20:37 +0000 Subject: [PATCH 690/760] Update byline styling From Josh: "We need to adjust the styling of the byline so that it works better within the title lock-up." --- components/x-live-blog-post/src/LiveBlogPost.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 941410aae..5606f9843 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -21,9 +21,9 @@ } .live-blog-post__byline { - @include oTypographySans($scale: -1, $weight: 'semibold'); - color: oColorsByName('black-70'); - margin-top: oSpacingByName('s1'); + @include oTypographySans($scale: 0, $line-height: 1.25); + color: oColorsByName('black-80'); + margin-top: oSpacingByName('s3'); margin-bottom: 20px; } From e41879f4190a41b1ef00c99af5ab988dc6d2a1f4 Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Mon, 1 Mar 2021 17:29:35 +0000 Subject: [PATCH 691/760] Remove overflow: auto from live blog post This was preventing us using `scroll-margin` to make the linking to the post via anchor better. --- components/x-live-blog-post/src/LiveBlogPost.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 5d28fc293..2f357abe6 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -3,7 +3,6 @@ @import 'o-colors/main'; .live-blog-post { - overflow: auto; border-bottom: 1px solid oColorsMix(black, paper, 20); margin-top: oSpacingByName('s8'); color: oColorsMix(black, paper, 90); From 4223c81c9feaf0f6b37a0f9883a3c855e56b1303 Mon Sep 17 00:00:00 2001 From: Jennifer Johnson <jkerr321@gmail.com> Date: Thu, 18 Mar 2021 06:31:26 +0000 Subject: [PATCH 692/760] Pass options into image-service function as an object rather than a string. Fixes headshots which hace stopped being blue. The function expects an object and is creating an incorrect url src string when the options are a string. --- components/x-teaser/src/Headshot.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/src/Headshot.jsx b/components/x-teaser/src/Headshot.jsx index fc5b204ab..813cb4aca 100644 --- a/components/x-teaser/src/Headshot.jsx +++ b/components/x-teaser/src/Headshot.jsx @@ -6,7 +6,7 @@ import imageService from './concerns/image-service' const DEFAULT_TINT = '054593,d6d5d3' export default ({ headshot, headshotTint }) => { - const options = `tint=${headshotTint || DEFAULT_TINT}` + const options = { tint: `${headshotTint || DEFAULT_TINT}` } return headshot ? ( <img From 3cb77f2b801029ce21aab577c33382c355b55b65 Mon Sep 17 00:00:00 2001 From: Jennifer Johnson <jkerr321@gmail.com> Date: Thu, 18 Mar 2021 06:58:24 +0000 Subject: [PATCH 693/760] Headshot fix: Update Jest snapshots to reflect correct url structure --- .../__snapshots__/snapshots.test.js.snap | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap index 63dfcb7ad..3ad732588 100644 --- a/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap +++ b/components/x-teaser/__tests__/__snapshots__/snapshots.test.js.snap @@ -251,7 +251,7 @@ exports[`x-teaser / snapshots renders a Hero teaser with opinion data 1`] = ` aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> @@ -815,7 +815,7 @@ exports[`x-teaser / snapshots renders a HeroNarrow teaser with opinion data 1`] aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> @@ -1329,7 +1329,7 @@ exports[`x-teaser / snapshots renders a HeroOverlay teaser with opinion data 1`] aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> @@ -1833,7 +1833,7 @@ exports[`x-teaser / snapshots renders a HeroVideo teaser with opinion data 1`] = aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> @@ -2403,7 +2403,7 @@ exports[`x-teaser / snapshots renders a Large teaser with opinion data 1`] = ` aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> @@ -2967,7 +2967,7 @@ exports[`x-teaser / snapshots renders a Small teaser with opinion data 1`] = ` aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> @@ -3481,7 +3481,7 @@ exports[`x-teaser / snapshots renders a SmallHeavy teaser with opinion data 1`] aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> @@ -4105,7 +4105,7 @@ exports[`x-teaser / snapshots renders a TopStory teaser with opinion data 1`] = aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> @@ -4716,7 +4716,7 @@ exports[`x-teaser / snapshots renders a TopStoryLandscape teaser with opinion da aria-hidden="true" className="o-teaser__headshot" height={75} - src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?0=t&1=i&2=n&3=t&4=%3D&5=0&6=5&7=4&8=5&9=9&10=3&11=%2C&12=d&13=6&14=d&15=5&16=d&17=3&source=next&fit=scale-down&dpr=2&width=75" + src="https://www.ft.com/__origami/service/image/v2/images/raw/fthead-v1%3Agideon-rachman?source=next&fit=scale-down&dpr=2&tint=054593%2Cd6d5d3&width=75" width={75} /> </div> From a3c2c7e006de6422615b68eb50cb66405b8c99f9 Mon Sep 17 00:00:00 2001 From: Nick Colley <2445413+nickcolley@users.noreply.github.com> Date: Mon, 22 Mar 2021 14:25:43 +0000 Subject: [PATCH 694/760] Update readme.md We added functionality to render a byline here: https://github.com/Financial-Times/x-dash/commit/15eddd1741e87ba405acd9f73958a0eaa161c92a This adds the documentation for that new property. --- components/x-live-blog-post/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index 20f5fabcc..725846c76 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -45,6 +45,7 @@ Feature | Type | Notes `postId` | String | Deprecated - Unique id to reference the content `title` | String | Title of the content `bodyHTML` | String | Body of the content +`byline` | String | Byline for the post, sometimes used to render the author's name. `content` | String | Deprecated - Body of the content `isBreakingNews` | Bool | When `true` displays "breaking news" tag `publishedDate` | String | ISO timestamp of publish date From 7148ddf6105f4589f58d2a9c180d459a6ff0f8ef Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Wed, 7 Apr 2021 14:32:38 +0100 Subject: [PATCH 695/760] Update payload to ensure that FTConsent_GDPR is set --- components/x-privacy-manager/src/__tests__/helpers.js | 1 + components/x-privacy-manager/src/actions.js | 1 + 2 files changed, 2 insertions(+) diff --git a/components/x-privacy-manager/src/__tests__/helpers.js b/components/x-privacy-manager/src/__tests__/helpers.js index 8ac2e4073..34635f33f 100644 --- a/components/x-privacy-manager/src/__tests__/helpers.js +++ b/components/x-privacy-manager/src/__tests__/helpers.js @@ -2,6 +2,7 @@ export const CONSENT_PROXY_HOST = 'https://consent.ft.com' export const CONSENT_PROXY_ENDPOINT = 'https://consent.ft.com/__consent/consent-record/FTPINK/abcde' export const buildPayload = (consent) => ({ + setConsentCookie: true, consentSource: 'consuming-app', data: { behaviouralAds: { diff --git a/components/x-privacy-manager/src/actions.js b/components/x-privacy-manager/src/actions.js index 0335b25b8..bbb9a66e3 100644 --- a/components/x-privacy-manager/src/actions.js +++ b/components/x-privacy-manager/src/actions.js @@ -28,6 +28,7 @@ function sendConsent({ consentApiUrl, onConsentSavedCallbacks, consentSource, co } const payload = { + setConsentCookie: true, formOfWordsId: fow.id, consentSource, data: { From bbec94181dd22714b1d01175383466ac5a5bcfaa Mon Sep 17 00:00:00 2001 From: Nick Colley <nick.colley@ft.com> Date: Mon, 8 Mar 2021 14:27:06 +0000 Subject: [PATCH 696/760] Remove unused listener logic We moved this into the applications for now, if there is a good abstraction in the future we may bring some more of this logic back into x-dash. --- components/x-live-blog-wrapper/readme.md | 87 +++++++----------- .../src/LiveBlogWrapper.jsx | 3 +- .../src/LiveEventListener.js | 62 ------------- .../src/__tests__/LiveEventListener.test.js | 90 ------------------- 4 files changed, 31 insertions(+), 211 deletions(-) delete mode 100644 components/x-live-blog-wrapper/src/LiveEventListener.js delete mode 100644 components/x-live-blog-wrapper/src/__tests__/LiveEventListener.test.js diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index d42472e97..e9d8411b2 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -35,20 +35,15 @@ All `x-` components are designed to be compatible with a variety of runtimes, no [jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ ### Client side rendering -This component can be used at the client side. To access the actions, a function needs to be passed into the actionsRef property of the LiveBlogWrapper element. +This component can be used at the client side. ```jsx import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; -const actionsRef = actions => { - // Use actions to insert, update and delete live blog posts -}; - <LiveBlogWrapper articleUrl="https://www.ft.com/content/live_blog_package_uuid" showShareButtons={true} id="live-blog-wrapper" posts={posts} - actionsRef={actionsRef} /> ``` @@ -81,64 +76,43 @@ import { hydrate } from '@financial-times/x-interaction'; hydrate(); ``` -### Live updates -This component exports a function named `listenToLiveBlogEvents` which is used for listening to client side live blog updates. These updates come in the form of server sent events sent by `next-live-event-api`. - -This function is used in slightly different ways when rendering the component at the client side vs rendering it at the server side. - -#### Client side rendering -A reference to the actions object should be passed as an argument when calling this function for a client side rendered component. -```jsx -import { LiveBlogWrapper, listenToLiveBlogEvents } from '@financial-times/x-live-blog-wrapper'; - -const actionsRef = actions => { - listenToLiveBlogEvents({ - liveBlogWrapperElementId: 'live-blog-wrapper', - liveBlogPackageUuid: 'package-uuid', - actions // for client side rendered component only - }); -}; +### Inserting posts on the client side -<LiveBlogWrapper articleUrl="https://www.ft.com/content/live_blog_package_uuid" - showShareButtons={true} - id="live-blog-wrapper" - posts={posts} - actionsRef={actionsRef} - /> -``` - -#### Server side rendering -This function should be called after hydrating the component if it is rendered at the server side. - -Server side: -```jsx -import { Serialiser, HydrationData } from '@financial-times/x-interaction'; -import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; - -const serialiser = new Serialiser(); - -<LiveBlogWrapper articleUrl="https://www.ft.com/content/live_blog_package_uuid" - showShareButtons={true} - id="live-blog-wrapper" - posts={posts} - serialiser={serialiser} /> -<HydrationData serialiser={serialiser} /> -``` +When live updates come in you can insert a new post by dispatching an action to the component's wrapper. Client side: ```js import { hydrate } from '@financial-times/x-interaction'; -import { listenToLiveBlogEvents } from '@financial-times/x-live-blog-wrapper'; hydrate(); -listenToLiveBlogEvents({ - liveBlogWrapperElementId: 'live-blog-wrapper', - liveBlogPackageUuid: 'package-uuid' -}); + +const wrapperElement = document.querySelector( + `[data-live-blog-wrapper-id="x-dash-element-id"]` +); + +const post = { + id: '00000000-0000-0000-0000-000000000000', + ... +}; + +const action = 'insert-post'; +// wrapperElement must be the last argument. +const args = [ + post, + wrapperElement +]; + +wrapperElement.dispatchEvent( + new CustomEvent('x-interaction.trigger-action', { + detail: { action, args }, + bubbles: true + }) +); ``` ### Client side events This component dispatches the following client side events to notify the consuming app about live updates. Consuming apps typically use these events to initialise Origami components on the newly rendered markup. + ```jsx <LiveBlogWrapper articleUrl="https://www.ft.com" showShareButtons={true} @@ -150,20 +124,19 @@ This component dispatches the following client side events to notify the consumi ```js ... -listenToLiveBlogEvents(); - const wrapperElement = document.querySelector( `[data-live-blog-wrapper-id="x-dash-element-id"]` ); wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', - (ev) => { - const { post } = ev.detail; + (event) => { + const { post } = event.detail; // post object contains data about a live blog post // post.id can be used to identify the newly rendered // LiveBlogPost element }); +``` ### Properties diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 4bf578df1..4dbab6f4a 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -1,7 +1,6 @@ import { h } from '@financial-times/x-engine' import { LiveBlogPost } from '@financial-times/x-live-blog-post' import { withActions } from '@financial-times/x-interaction' -import { listenToLiveBlogEvents } from './LiveEventListener' import { normalisePost } from './normalisePost' import { dispatchEvent } from './dispatchEvent' import { registerComponent } from '@financial-times/x-interaction' @@ -59,4 +58,4 @@ const LiveBlogWrapper = withLiveBlogWrapperActions(BaseLiveBlogWrapper) // This enables the component to work with x-interaction hydration registerComponent(LiveBlogWrapper, 'LiveBlogWrapper') -export { LiveBlogWrapper, listenToLiveBlogEvents } +export { LiveBlogWrapper } diff --git a/components/x-live-blog-wrapper/src/LiveEventListener.js b/components/x-live-blog-wrapper/src/LiveEventListener.js deleted file mode 100644 index 9c3343040..000000000 --- a/components/x-live-blog-wrapper/src/LiveEventListener.js +++ /dev/null @@ -1,62 +0,0 @@ -import { normalisePost } from './normalisePost' - -const parsePost = (event) => { - const post = JSON.parse(event.data) - const normalisedPost = normalisePost(post) - - if (!normalisedPost || !normalisedPost.id) { - return - } - - return normalisedPost -} - -const listenToLiveBlogEvents = ({ liveBlogWrapperElementId, liveBlogPackageUuid, actions }) => { - const wrapper = document.querySelector(`[data-live-blog-wrapper-id="${liveBlogWrapperElementId}"]`) - - const invokeAction = (action, args) => { - if (actions) { - // When the component is rendered at the client side, we get a reference to the - // actions via setting the actionsRef property. - // - // In that case 'actions' argument should be passed when calling the - // listenToLiveBlogEvents function. We use those actions directly when that - // argument is defined. - // - // For more information: - // https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-interaction#triggering-actions-externally - actions[action](...args) - } else { - // When the component is rendered at the server side, we don't have a reference to - // the actions object. HydrationWrapper in x-interaction listens to this specific - // event and triggers the action supplied in the event detail. - // - // If no 'actions' argument is passed when calling listenToLiveBlogEvents - // function, we assume the component is rendered at the server side and trigger - // the actions using this method. - wrapper.dispatchEvent( - new CustomEvent('x-interaction.trigger-action', { detail: { action, args }, bubbles: true }) - ) - } - } - - // Allow `next-live-event-api` endpoint URL to be set in development. - const baseUrl = - typeof LIVE_EVENT_API_URL !== 'undefined' ? LIVE_EVENT_API_URL : 'https://next-live-event.ft.com' // eslint-disable-line no-undef - - const eventSource = new EventSource(`${baseUrl}/v2/liveblog/${liveBlogPackageUuid}`, { - withCredentials: true - }) - - eventSource.addEventListener('insert-post', (event) => { - const post = parsePost(event) - - if (!post) { - return - } - - invokeAction('insertPost', [post, wrapper]) - }) -} - -export { listenToLiveBlogEvents } diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveEventListener.test.js b/components/x-live-blog-wrapper/src/__tests__/LiveEventListener.test.js deleted file mode 100644 index 18416d58f..000000000 --- a/components/x-live-blog-wrapper/src/__tests__/LiveEventListener.test.js +++ /dev/null @@ -1,90 +0,0 @@ -import { listenToLiveBlogEvents } from '../LiveEventListener' - -const addEventListenerMock = jest.fn() -const EventSourceMock = jest.fn(() => { - return { - addEventListener: addEventListenerMock - } -}) -global.EventSource = EventSourceMock - -const dispatchEventMock = jest.fn() -jest.spyOn(document, 'querySelector').mockImplementation(() => { - return { - dispatchEvent: dispatchEventMock - } -}) - -describe('liveEventListener', () => { - describe('EventSource', () => { - it('should default to the live URL', () => { - listenToLiveBlogEvents({}) - expect(EventSourceMock).toHaveBeenCalledWith('https://next-live-event.ft.com/v2/liveblog/undefined', { - withCredentials: true - }) - }) - - it('should allow baseUrl to be overriden for testing', () => { - global.LIVE_EVENT_API_URL = 'http://localhost:5000' - listenToLiveBlogEvents({}) - expect(EventSourceMock).toHaveBeenCalledWith('http://localhost:5000/v2/liveblog/undefined', { - withCredentials: true - }) - delete global.LIVE_EVENT_API_URL - }) - - it('should use the liveBlogPackageUuid as a source', () => { - listenToLiveBlogEvents({ liveBlogPackageUuid: 1234 }) - expect(EventSourceMock).toHaveBeenCalledWith('https://next-live-event.ft.com/v2/liveblog/1234', { - withCredentials: true - }) - }) - }) - - describe('listening to events', () => { - afterAll(() => { - addEventListenerMock.mockReset() - dispatchEventMock.mockReset() - }) - it('throws exception if no stringified data', () => { - addEventListenerMock.mockImplementation((event, handler) => { - handler({}) - }) - expect(() => { - listenToLiveBlogEvents({}) - }).toThrow('Unexpected token') - }) - it('if actions are supplied, they should be called', () => { - addEventListenerMock.mockImplementation((event, handler) => { - handler({ - data: JSON.stringify({ - id: 1234 - }) - }) - }) - listenToLiveBlogEvents({ - actions: { - insertPost: (event, wrapper) => { - expect(wrapper).toEqual({ - dispatchEvent: dispatchEventMock - }) - expect(event).toEqual({ - id: 1234 - }) - } - } - }) - }) - it('if no actions supplied, then an event should be dispatched', () => { - addEventListenerMock.mockImplementation((event, handler) => { - handler({ - data: JSON.stringify({ - id: 1234 - }) - }) - }) - listenToLiveBlogEvents({}) - expect(dispatchEventMock).toHaveBeenCalled() - }) - }) -}) From a2e6b9c630d7e7fc8070e62707e2546f636cd01c Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Thu, 8 Apr 2021 16:10:46 +0100 Subject: [PATCH 697/760] Send setConsentCookie only when legislationId is 'gdpr' --- .../{tsconfig.json => jsconfig.json} | 1 - .../src/__tests__/helpers.js | 110 +++++++++++++----- .../src/__tests__/messaging.test.jsx | 82 ++++++------- .../src/__tests__/state.test.jsx | 79 ++++++------- components/x-privacy-manager/src/actions.js | 13 ++- .../x-privacy-manager/src/components/form.jsx | 4 +- .../src/components/messages.jsx | 2 +- .../src/components/radio-btn.jsx | 2 +- .../x-privacy-manager/src/privacy-manager.jsx | 5 +- components/x-privacy-manager/src/utils.js | 6 +- .../storybook/stories/consent-accepted.js | 2 +- .../stories/consent-indeterminate.js | 2 +- .../storybook/stories/save-failed.js | 2 +- .../typings/x-privacy-manager.d.ts | 5 +- 14 files changed, 182 insertions(+), 133 deletions(-) rename components/x-privacy-manager/{tsconfig.json => jsconfig.json} (92%) diff --git a/components/x-privacy-manager/tsconfig.json b/components/x-privacy-manager/jsconfig.json similarity index 92% rename from components/x-privacy-manager/tsconfig.json rename to components/x-privacy-manager/jsconfig.json index ceec3d31c..85f3f0af0 100644 --- a/components/x-privacy-manager/tsconfig.json +++ b/components/x-privacy-manager/jsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { "outDir": "dist/types", - "allowJs": true, "target": "es2015", "module": "commonjs", "strict": true, diff --git a/components/x-privacy-manager/src/__tests__/helpers.js b/components/x-privacy-manager/src/__tests__/helpers.js index 34635f33f..2a59eed17 100644 --- a/components/x-privacy-manager/src/__tests__/helpers.js +++ b/components/x-privacy-manager/src/__tests__/helpers.js @@ -1,39 +1,58 @@ +const React = require('react') + +// eslint-disable-next-line no-unused-vars +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') + +import { PrivacyManager } from '../privacy-manager' + export const CONSENT_PROXY_HOST = 'https://consent.ft.com' export const CONSENT_PROXY_ENDPOINT = 'https://consent.ft.com/__consent/consent-record/FTPINK/abcde' -export const buildPayload = (consent) => ({ - setConsentCookie: true, - consentSource: 'consuming-app', - data: { - behaviouralAds: { - onsite: { - fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', - lbi: true, - source: 'consuming-app', - status: consent +/** + * + * @param {{ + * setConsentCookie: boolean, + * consent: boolean + * }} + * @returns + */ +export const buildPayload = ({ setConsentCookie, consent }) => { + return { + setConsentCookie, + consentSource: 'consuming-app', + data: { + behaviouralAds: { + onsite: { + fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', + lbi: true, + source: 'consuming-app', + status: consent + } + }, + demographicAds: { + onsite: { + fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', + lbi: true, + source: 'consuming-app', + status: consent + } + }, + programmaticAds: { + onsite: { + fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', + lbi: true, + source: 'consuming-app', + status: consent + } } }, - demographicAds: { - onsite: { - fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', - lbi: true, - source: 'consuming-app', - status: consent - } - }, - programmaticAds: { - onsite: { - fow: 'privacyCCPA/H0IeyQBalorD.6nTqqzhNTKECSgOPJCG', - lbi: true, - source: 'consuming-app', - status: consent - } - } - }, - cookieDomain: '.ft.com', - formOfWordsId: 'privacyCCPA' -}) + cookieDomain: '.ft.com', + formOfWordsId: 'privacyCCPA' + } +} +/** @type {XPrivacyManager.BasePrivacyManagerProps} */ export const defaultProps = { userId: 'abcde', legislationId: 'ccpa', @@ -48,5 +67,36 @@ export const defaultProps = { actions: { onConsentChange: jest.fn(() => {}), sendConsent: jest.fn().mockReturnValue({ _response: { ok: undefined } }) + }, + onConsentSavedCallbacks: [jest.fn(), jest.fn()] +} + +/** + * Configure an instance of PrivacyManager and set up + * - Handlers for submit events + * - Post-submission callbacks + * + * @param {Partial<XPrivacyManager.BasePrivacyManagerProps>} propOverrides + * + * @returns {{ + * subject: ReactWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>; + * callbacks: XPrivacyManager.OnSaveCallback[] | jest.Mock<any, any>[]; + * submitConsent(value: boolean): Promise<void>; + * }} + */ +export function setupPrivacyManager(propOverrides = {}) { + const props = Object.assign({}, defaultProps, propOverrides) + const subject = mount(<PrivacyManager {...props} />) + + return { + subject, + callbacks: props.onConsentSavedCallbacks, + async submitConsent(value) { + await subject.find(`input[value="${value}"]`).first().prop('onChange')(undefined) + await subject.find('form').first().prop('onSubmit')(undefined) + + // Reconcile snapshot with state + subject.update() + } } } diff --git a/components/x-privacy-manager/src/__tests__/messaging.test.jsx b/components/x-privacy-manager/src/__tests__/messaging.test.jsx index eacf8e766..538a0901a 100644 --- a/components/x-privacy-manager/src/__tests__/messaging.test.jsx +++ b/components/x-privacy-manager/src/__tests__/messaging.test.jsx @@ -1,21 +1,21 @@ -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); +const { h } = require('@financial-times/x-engine') +const { mount } = require('@financial-times/x-test-utils/enzyme') -import { defaultProps } from './helpers'; +import { defaultProps } from './helpers' -import { BasePrivacyManager } from '../privacy-manager'; +import { BasePrivacyManager } from '../privacy-manager' function findMessageComponent(props) { - const subject = mount(<BasePrivacyManager {...props} />); - const messages = subject.find('[data-o-component="o-message"]'); - const message = messages.first(); - const link = message.find('[data-component="referrer-link"]'); + const subject = mount(<BasePrivacyManager {...props} />) + const messages = subject.find('[data-o-component="o-message"]') + const message = messages.first() + const link = message.find('[data-component="referrer-link"]') return { messages, message, - link, - }; + link + } } describe('x-privacy-manager', () => { @@ -25,50 +25,50 @@ describe('x-privacy-manager', () => { consent: true, legislation: ['ccpa'], isLoading: false, - _response: undefined, - }; + _response: undefined + } it('None by default', () => { - const { messages } = findMessageComponent(messageProps); - expect(messages).toHaveLength(0); - }); + const { messages } = findMessageComponent(messageProps) + expect(messages).toHaveLength(0) + }) it('While loading', () => { - const { messages, message } = findMessageComponent({ ...messageProps, isLoading: true }); - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--neutral'); - }); + const { messages, message } = findMessageComponent({ ...messageProps, isLoading: true }) + expect(messages).toHaveLength(1) + expect(message).toHaveClassName('o-message--neutral') + }) it('On receiving a response with a status of 200', () => { - const _response = { ok: true, status: 200 }; - const { messages, message, link } = findMessageComponent({ ...messageProps, _response }); + const _response = { ok: true, status: 200 } + const { messages, message, link } = findMessageComponent({ ...messageProps, _response }) - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--success'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - }); + expect(messages).toHaveLength(1) + expect(message).toHaveClassName('o-message--success') + expect(link).toHaveProp('href', 'https://www.ft.com/') + }) it('On receiving a response with a non-200 status', () => { - const _response = { ok: false, status: 400 }; - const { messages, message, link } = findMessageComponent({ ...messageProps, _response }); + const _response = { ok: false, status: 400 } + const { messages, message, link } = findMessageComponent({ ...messageProps, _response }) - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--error'); - expect(link).toHaveProp('href', 'https://www.ft.com/'); - }); + expect(messages).toHaveLength(1) + expect(message).toHaveClassName('o-message--error') + expect(link).toHaveProp('href', 'https://www.ft.com/') + }) it('On receiving any response with referrer undefined', () => { - const _response = { ok: false, status: 400 }; - const referrer = undefined; + const _response = { ok: false, status: 400 } + const referrer = undefined const { messages, message, link } = findMessageComponent({ ...messageProps, referrer, - _response, - }); + _response + }) - expect(messages).toHaveLength(1); - expect(message).toHaveClassName('o-message--error'); - expect(link).toHaveLength(0); - }); - }); -}); + expect(messages).toHaveLength(1) + expect(message).toHaveClassName('o-message--error') + expect(link).toHaveLength(0) + }) + }) +}) diff --git a/components/x-privacy-manager/src/__tests__/state.test.jsx b/components/x-privacy-manager/src/__tests__/state.test.jsx index eac9093dc..1b6454970 100644 --- a/components/x-privacy-manager/src/__tests__/state.test.jsx +++ b/components/x-privacy-manager/src/__tests__/state.test.jsx @@ -1,39 +1,13 @@ -const { h } = require('@financial-times/x-engine') -const { mount } = require('@financial-times/x-test-utils/enzyme') const fetchMock = require('fetch-mock') import * as helpers from './helpers' -import { PrivacyManager } from '../privacy-manager' - function getLastFetchPayload() { return JSON.parse(fetchMock.lastOptions().body) } describe('x-privacy-manager', () => { describe('handling consent choices', () => { - function setup(propOverrides = {}) { - const props = { - ...helpers.defaultProps, - onConsentSavedCallbacks: [jest.fn(), jest.fn()], - ...propOverrides - } - const subject = mount(<PrivacyManager {...props} />) - - return { - subject, - callbacks: props.onConsentSavedCallbacks, - async submitConsent(value) { - // Switch consent to false and submit form - await subject.find(`input[value="${value}"]`).first().prop('onChange')(undefined) - await subject.find('form').first().prop('onSubmit')(undefined) - - // Reconcile snapshot with state - subject.update() - } - } - } - beforeEach(() => { fetchMock.reset() fetchMock.config.overwriteRoutes = true @@ -45,25 +19,25 @@ describe('x-privacy-manager', () => { }) it('handles consecutive changes of consent', async () => { - let payload - const { subject, callbacks, submitConsent } = setup({ consent: true }) + let expectedPayload + const { subject, callbacks, submitConsent } = helpers.setupPrivacyManager({ consent: true }) const optInInput = subject.find('[data-trackable="ccpa-advertising-toggle-allow"]').first() await submitConsent(false) // Check fetch and both callbacks were run with correct `payload` values - payload = helpers.buildPayload(false) - expect(getLastFetchPayload()).toEqual(payload) - expect(callbacks[0]).toHaveBeenCalledWith(null, { payload, consent: false }) - expect(callbacks[1]).toHaveBeenCalledWith(null, { payload, consent: false }) + expectedPayload = helpers.buildPayload({ setConsentCookie: false, consent: false }) + expect(getLastFetchPayload()).toEqual(expectedPayload) + expect(callbacks[0]).toHaveBeenCalledWith(null, { payload: expectedPayload, consent: false }) + expect(callbacks[1]).toHaveBeenCalledWith(null, { payload: expectedPayload, consent: false }) await submitConsent(true) // Check fetch and both callbacks were run with correct `payload` values - payload = helpers.buildPayload(true) - expect(getLastFetchPayload()).toEqual(payload) - expect(callbacks[0]).toHaveBeenCalledWith(null, { payload, consent: true }) - expect(callbacks[1]).toHaveBeenCalledWith(null, { payload, consent: true }) + expectedPayload = helpers.buildPayload({ setConsentCookie: false, consent: true }) + expect(getLastFetchPayload()).toEqual(expectedPayload) + expect(callbacks[0]).toHaveBeenCalledWith(null, { payload: expectedPayload, consent: true }) + expect(callbacks[1]).toHaveBeenCalledWith(null, { payload: expectedPayload, consent: true }) // Verify that confimatory nmessage is displayed const message = subject.find('[data-o-component="o-message"]').first() @@ -74,20 +48,23 @@ describe('x-privacy-manager', () => { }) it('when provided, passes the cookieDomain prop in the fetch and callback payload', async () => { - const { callbacks, submitConsent } = setup({ cookieDomain: '.ft.com' }) - const payload = { ...helpers.buildPayload(false), cookieDomain: '.ft.com' } + const { callbacks, submitConsent } = helpers.setupPrivacyManager({ cookieDomain: '.ft.com' }) + const expectedPayload = { + ...helpers.buildPayload({ setConsentCookie: false, consent: false }), + cookieDomain: '.ft.com' + } await submitConsent(false) // Check fetch and both callbacks were run with correct `payload` values - expect(getLastFetchPayload()).toEqual(payload) - expect(callbacks[0]).toHaveBeenCalledWith(null, { payload, consent: false }) - expect(callbacks[1]).toHaveBeenCalledWith(null, { payload, consent: false }) + expect(getLastFetchPayload()).toEqual(expectedPayload) + expect(callbacks[0]).toHaveBeenCalledWith(null, { payload: expectedPayload, consent: false }) + expect(callbacks[1]).toHaveBeenCalledWith(null, { payload: expectedPayload, consent: false }) }) it('passes error object to callbacks when fetch fails', async () => { - const { callbacks, submitConsent } = setup() - const payload = helpers.buildPayload(false) + const { callbacks, submitConsent } = helpers.setupPrivacyManager() + const expectedPayload = helpers.buildPayload({ setConsentCookie: false, consent: false }) // Override fetch-mock to fail requests fetchMock.mock(helpers.CONSENT_PROXY_ENDPOINT, { status: 500 }, { delay: 500 }) @@ -95,14 +72,26 @@ describe('x-privacy-manager', () => { await submitConsent(false) // calls fetch with the correct payload - expect(getLastFetchPayload()).toEqual(payload) + expect(getLastFetchPayload()).toEqual(expectedPayload) // Calls both callbacks with an error as first argument callbacks.forEach((callback) => { const [errorArgument, resultArgument] = callback.mock.calls.pop() expect(errorArgument).toBeInstanceOf(Error) - expect(resultArgument).toEqual({ payload, consent: false }) + expect(resultArgument).toEqual({ payload: expectedPayload, consent: false }) }) }) + + it('Sends legislation-specific values (e.g. setConsentCookie)', async () => { + const expectedPayload = helpers.buildPayload({ setConsentCookie: true, consent: false }) + const { submitConsent } = helpers.setupPrivacyManager({ + legislationId: 'gdpr', + consent: true + }) + + await submitConsent(false) + + expect(getLastFetchPayload()).toEqual(expectedPayload) + }) }) }) diff --git a/components/x-privacy-manager/src/actions.js b/components/x-privacy-manager/src/actions.js index bbb9a66e3..3b67172cd 100644 --- a/components/x-privacy-manager/src/actions.js +++ b/components/x-privacy-manager/src/actions.js @@ -9,10 +9,17 @@ function onConsentChange(consent) { * - consentSource: (e.g. 'next-control-centre') * - cookieDomain: (e.g. '.thebanker.com') * - * @param {import("../types").SendConsentProps} args + * @param {XPrivacyManager.SendConsentProps} args * @returns {({ isLoading, consent }: { isLoading: boolean, consent: boolean }) => Promise<{_response: _Response}>} */ -function sendConsent({ consentApiUrl, onConsentSavedCallbacks, consentSource, cookieDomain, fow }) { +function sendConsent({ + setConsentCookie, + consentApiUrl, + onConsentSavedCallbacks, + consentSource, + cookieDomain, + fow +}) { let res return async ({ isLoading, consent }) => { @@ -28,7 +35,7 @@ function sendConsent({ consentApiUrl, onConsentSavedCallbacks, consentSource, co } const payload = { - setConsentCookie: true, + setConsentCookie, formOfWordsId: fow.id, consentSource, data: { diff --git a/components/x-privacy-manager/src/components/form.jsx b/components/x-privacy-manager/src/components/form.jsx index bb9513448..790474c31 100644 --- a/components/x-privacy-manager/src/components/form.jsx +++ b/components/x-privacy-manager/src/components/form.jsx @@ -2,10 +2,10 @@ import { h } from '@financial-times/x-engine' import s from '../privacy-manager.scss' /** - * @param {import('../../typings/x-privacy-manager').FormProps} args + * @param {XPrivacyManager.FormProps} args */ export const Form = ({ consent, consentApiUrl, sendConsent, trackingKeys, buttonText, children }) => { - /** @type {import('../../typings/x-privacy-manager').TrackingKey} */ + /** @type {XPrivacyManager.TrackingKey} */ const consentAction = consent ? 'consent-allow' : 'consent-block' const btnTrackingId = trackingKeys[consentAction] const isDisabled = typeof consent === 'undefined' diff --git a/components/x-privacy-manager/src/components/messages.jsx b/components/x-privacy-manager/src/components/messages.jsx index ff72b8fce..b4b38f4b4 100644 --- a/components/x-privacy-manager/src/components/messages.jsx +++ b/components/x-privacy-manager/src/components/messages.jsx @@ -82,7 +82,7 @@ export function LoadingMessage() { /** * @param {boolean} isLoading - * @param {import('../../typings/x-privacy-manager')._Response} response + * @param {XPrivacyManager._Response} response * @param {string} referrer */ export function renderMessage(isLoading, response, referrer) { diff --git a/components/x-privacy-manager/src/components/radio-btn.jsx b/components/x-privacy-manager/src/components/radio-btn.jsx index 1c5a6627a..9071b5371 100644 --- a/components/x-privacy-manager/src/components/radio-btn.jsx +++ b/components/x-privacy-manager/src/components/radio-btn.jsx @@ -7,7 +7,7 @@ import s from './radio-btn.scss' * name: string, * type: "allow" | "block", * checked: boolean, - * trackingKeys: import('../../typings/x-privacy-manager').TrackingKeys, + * trackingKeys: XPrivacyManager.TrackingKeys, * onChange: (value: boolean) => void, * }} args * diff --git a/components/x-privacy-manager/src/privacy-manager.jsx b/components/x-privacy-manager/src/privacy-manager.jsx index 964cdc0d7..497b04843 100644 --- a/components/x-privacy-manager/src/privacy-manager.jsx +++ b/components/x-privacy-manager/src/privacy-manager.jsx @@ -14,7 +14,7 @@ const defaultButtonText = { } /** - * @param {import('../typings/x-privacy-manager').BasePrivacyManagerProps} Props + * @param {XPrivacyManager.BasePrivacyManagerProps} Props */ export function BasePrivacyManager({ userId, @@ -59,7 +59,7 @@ export function BasePrivacyManager({ onChange: onConsentChange }) - /** @type {import('../typings/x-privacy-manager').FormProps} */ + /** @type {XPrivacyManager.FormProps} */ const formProps = { consent, consentApiUrl, @@ -67,6 +67,7 @@ export function BasePrivacyManager({ buttonText, sendConsent: () => { return sendConsent({ + setConsentCookie: legislationId === 'gdpr', consentApiUrl, onConsentSavedCallbacks, consentSource, diff --git a/components/x-privacy-manager/src/utils.js b/components/x-privacy-manager/src/utils.js index 93a4c371c..01f760fba 100644 --- a/components/x-privacy-manager/src/utils.js +++ b/components/x-privacy-manager/src/utils.js @@ -1,4 +1,4 @@ -/** @type {import("../types").TrackingKey[]} */ +/** @type {XPrivacyManager.TrackingKey[]} */ const trackingKeys = [ 'advertising-toggle-block', 'advertising-toggle-allow', @@ -12,7 +12,7 @@ const trackingKeys = [ * * @param {string} legislationId * - * @returns {import("@financial-times/x-privacy-manager").TrackingKeys} + * @returns {XPrivacyManager.TrackingKeys} */ export function getTrackingKeys(legislationId) { /** @type Record<TrackingKey, string> */ @@ -32,7 +32,7 @@ export function getTrackingKeys(legislationId) { * cookieDomain?: string; * }} param * - * @returns {import("@financial-times/x-privacy-manager").ConsentProxyEndpoint} + * @returns {XPrivacyManager.ConsentProxyEndpoint} */ export function getConsentProxyEndpoints({ userId, diff --git a/components/x-privacy-manager/storybook/stories/consent-accepted.js b/components/x-privacy-manager/storybook/stories/consent-accepted.js index 292812e12..dfff5d0f7 100644 --- a/components/x-privacy-manager/storybook/stories/consent-accepted.js +++ b/components/x-privacy-manager/storybook/stories/consent-accepted.js @@ -2,7 +2,7 @@ import { StoryContainer } from '../story-container' import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' /** - * @param {import('../../typings/x-privacy-manager').PrivacyManagerProps} args + * @param {XPrivacyManager.PrivacyManagerProps} args */ export const ConsentAccepted = (args) => { getFetchMock(200) diff --git a/components/x-privacy-manager/storybook/stories/consent-indeterminate.js b/components/x-privacy-manager/storybook/stories/consent-indeterminate.js index cafef5540..b538a4284 100644 --- a/components/x-privacy-manager/storybook/stories/consent-indeterminate.js +++ b/components/x-privacy-manager/storybook/stories/consent-indeterminate.js @@ -2,7 +2,7 @@ import { StoryContainer } from '../story-container' import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' /** - * @param {import('../../typings/x-privacy-manager').PrivacyManagerProps} args + * @param {XPrivacyManager.PrivacyManagerProps} args */ export const ConsentIndeterminate = (args) => { getFetchMock(200) diff --git a/components/x-privacy-manager/storybook/stories/save-failed.js b/components/x-privacy-manager/storybook/stories/save-failed.js index ac1d88bf7..448942cd9 100644 --- a/components/x-privacy-manager/storybook/stories/save-failed.js +++ b/components/x-privacy-manager/storybook/stories/save-failed.js @@ -2,7 +2,7 @@ import { StoryContainer } from '../story-container' import { defaultArgs, defaultArgTypes, getFetchMock } from '../data' /** - * @param {import('../../typings/x-privacy-manager').PrivacyManagerProps} args + * @param {XPrivacyManager.PrivacyManagerProps} args */ export const SaveFailed = (args) => { getFetchMock(500) diff --git a/components/x-privacy-manager/typings/x-privacy-manager.d.ts b/components/x-privacy-manager/typings/x-privacy-manager.d.ts index 55939f16d..7c6db29d8 100644 --- a/components/x-privacy-manager/typings/x-privacy-manager.d.ts +++ b/components/x-privacy-manager/typings/x-privacy-manager.d.ts @@ -29,9 +29,10 @@ interface ConsentPayload { data: ConsentData } -type OnSaveCallback = (err: null | Error, data: { consent: boolean; payload: ConsentPayload }) => void +export type OnSaveCallback = (err: null | Error, data: { consent: boolean; payload: ConsentPayload }) => void export interface SendConsentProps { + setConsentCookie: boolean consentApiUrl: string onConsentSavedCallbacks: OnSaveCallback[] consentSource: string @@ -96,3 +97,5 @@ export interface FormProps { } export { PrivacyManager } from '../src/privacy-manager' + +export as namespace XPrivacyManager From 900023f3a5b37c95f7c2e0fc9e785de651541f15 Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Thu, 13 May 2021 17:21:02 +0100 Subject: [PATCH 698/760] convert customSlotContent and customSlotPosition into arrays for --- .../__tests__/lib/transform.test.js | 24 +++++++++++++++++++ .../x-teaser-timeline/src/lib/transform.js | 16 ++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js index df4d9150a..c1b70039f 100644 --- a/components/x-teaser-timeline/__tests__/lib/transform.test.js +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -298,5 +298,29 @@ describe('buildModel', () => { groupedItems[2] ]) }) + test('returns correct model for multiple custom slots', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: [{ foo: 1 }, { bar: 2 }], + customSlotPosition: [0, 3] + }) + expect(result).toEqual([ + { + ...groupedItems[0], + items: [ + { foo: 1 }, + groupedItems[0].items[0], + groupedItems[0].items[1], + groupedItems[0].items[2], + { bar: 2 }, + groupedItems[0].items[3] + ] + }, + groupedItems[1], + groupedItems[2] + ]) + }) }) }) diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index fab1c91dd..04cd5d08a 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -165,15 +165,19 @@ export const buildModel = ({ latestItemsTime, latestItemsAgeHours }) - if (itemGroups.length > 0 && customSlotContent) { - const insertPosition = Math.min(customSlotPosition, items.length) - const insert = getGroupAndIndex(itemGroups, insertPosition) - const copyOfItems = [...itemGroups[insert.group].items] + customSlotContent = Array.isArray(customSlotContent) ? customSlotContent : [customSlotContent] + customSlotPosition = Array.isArray(customSlotPosition) ? customSlotPosition : [customSlotPosition] + + customSlotContent.forEach((item, index) => { + const insertPosition = Math.min(customSlotPosition[index], items.length) + const insert = getGroupAndIndex(itemGroups, insertPosition) + const copyOfItems = [...itemGroups[insert.group].items] - copyOfItems.splice(insert.index, 0, customSlotContent) + copyOfItems.splice(insert.index, 0, item) - itemGroups[insert.group].items = copyOfItems + itemGroups[insert.group].items = copyOfItems + }) } return itemGroups } From 198e81bcd2d7ffc82c1738f0091698b4212239c2 Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Tue, 18 May 2021 16:20:48 +0100 Subject: [PATCH 699/760] updated readme and additional test --- .../__tests__/lib/transform.test.js | 27 +++++++++++++++++++ components/x-teaser-timeline/readme.md | 4 +-- .../x-teaser-timeline/src/lib/transform.js | 19 ++++++++----- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js index c1b70039f..dfa4288f5 100644 --- a/components/x-teaser-timeline/__tests__/lib/transform.test.js +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -322,5 +322,32 @@ describe('buildModel', () => { groupedItems[2] ]) }) + test('returns correct model for multiple custom slots off end of all groups', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: [{ foo: 1 }, { bar: 2 }], + customSlotPosition: [0, 10] + }) + expect(result).toEqual([ + { + ...groupedItems[0], + items: [ + { foo: 1 }, + groupedItems[0].items[0], + groupedItems[0].items[1], + groupedItems[0].items[2], + { bar: 2 }, + groupedItems[0].items[3] + ] + }, + groupedItems[1], + { + ...groupedItems[2], + items: [...groupedItems[2].items, { bar: 2 }] + } + ]) + }) }) }) diff --git a/components/x-teaser-timeline/readme.md b/components/x-teaser-timeline/readme.md index 65c517508..ed763b980 100644 --- a/components/x-teaser-timeline/readme.md +++ b/components/x-teaser-timeline/readme.md @@ -56,8 +56,8 @@ Feature | Type | Notes `localTodayDate` | String | (Defaults using runtime clock) ISO format YYYY-MM-DD representating today's date in the user's timezone. `latestItemsTime` | String | ISO time (HH:mm:ss). If provided, will be used in combination with `localTodayDate` to render today's items into separate "Latest" and "Earlier" groups. `showSaveButtons` | Boolean | (Default to true). Option to hide x-article-save-buttons if they are not needed. Those buttons will get their saved/unsaved state from a `saved` property of the content item. -`customSlotContent` | String | Content to insert at `customSlotPosition`. -`customSlotPosition` | Number | (Default is 2). Where to insert `customSlotContent`. The custom content will be inserted after the item at this position number. If this position is greater than the number items to render, then it will be inserted last. +`customSlotContent` | String or Array | Content to insert at `customSlotPosition`. +`customSlotPosition` | Number or Array | (Default is 2). Where to insert `customSlotContent`. The custom content will be inserted after the item at this position number. If this position is greater than the number items to render, then it will be inserted last. `csrfToken` | String | A CSRF token that will be used by the save buttons (if shown). `latestItemsAgeHours`| Number | (Optional). If provided, used to calculate a cutoff time before which no article will count as "latest", regardless of the value of `latestItemsTime`. If omitted, articles before midnight this morning will not count as "latest". diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index 04cd5d08a..e601c2313 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -166,18 +166,23 @@ export const buildModel = ({ latestItemsAgeHours }) if (itemGroups.length > 0 && customSlotContent) { - customSlotContent = Array.isArray(customSlotContent) ? customSlotContent : [customSlotContent] - customSlotPosition = Array.isArray(customSlotPosition) ? customSlotPosition : [customSlotPosition] - - customSlotContent.forEach((item, index) => { - const insertPosition = Math.min(customSlotPosition[index], items.length) + const customSlotContentArray = Array.isArray(customSlotContent) ? customSlotContent : [customSlotContent] + const customSlotPositionArray = Array.isArray(customSlotPosition) + ? customSlotPosition + : [customSlotPosition] + + for (const [index, slotContent] of customSlotContentArray.entries()) { + const insertPosition = Math.min( + customSlotPositionArray[index], + items.length + customSlotPositionArray.length - 1 + ) const insert = getGroupAndIndex(itemGroups, insertPosition) const copyOfItems = [...itemGroups[insert.group].items] - copyOfItems.splice(insert.index, 0, item) + copyOfItems.splice(insert.index, 0, slotContent) itemGroups[insert.group].items = copyOfItems - }) + } } return itemGroups } From 07e7e536779289b681ec01e5d47507ce15efb9c4 Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Tue, 18 May 2021 16:34:56 +0100 Subject: [PATCH 700/760] updated tests --- components/x-teaser-timeline/__tests__/lib/transform.test.js | 1 - components/x-teaser-timeline/src/lib/transform.js | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js index dfa4288f5..f3f2a5c30 100644 --- a/components/x-teaser-timeline/__tests__/lib/transform.test.js +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -338,7 +338,6 @@ describe('buildModel', () => { groupedItems[0].items[0], groupedItems[0].items[1], groupedItems[0].items[2], - { bar: 2 }, groupedItems[0].items[3] ] }, diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index e601c2313..de1ffa604 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -167,9 +167,7 @@ export const buildModel = ({ }) if (itemGroups.length > 0 && customSlotContent) { const customSlotContentArray = Array.isArray(customSlotContent) ? customSlotContent : [customSlotContent] - const customSlotPositionArray = Array.isArray(customSlotPosition) - ? customSlotPosition - : [customSlotPosition] + const customSlotPositionArray = Array.isArray(customSlotPosition) ? customSlotPosition : [customSlotPosition] for (const [index, slotContent] of customSlotContentArray.entries()) { const insertPosition = Math.min( From 52c91a919784cf3f152bc6628456f68729d43336 Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Thu, 20 May 2021 11:26:02 +0100 Subject: [PATCH 701/760] changed insertPosition to be based on index of slot, instead of length of slot array --- components/x-teaser-timeline/src/lib/transform.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index de1ffa604..b297a9295 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -167,13 +167,12 @@ export const buildModel = ({ }) if (itemGroups.length > 0 && customSlotContent) { const customSlotContentArray = Array.isArray(customSlotContent) ? customSlotContent : [customSlotContent] - const customSlotPositionArray = Array.isArray(customSlotPosition) ? customSlotPosition : [customSlotPosition] + const customSlotPositionArray = Array.isArray(customSlotPosition) + ? customSlotPosition + : [customSlotPosition] for (const [index, slotContent] of customSlotContentArray.entries()) { - const insertPosition = Math.min( - customSlotPositionArray[index], - items.length + customSlotPositionArray.length - 1 - ) + const insertPosition = Math.min(customSlotPositionArray[index], items.length + index) const insert = getGroupAndIndex(itemGroups, insertPosition) const copyOfItems = [...itemGroups[insert.group].items] From bbdd9ab588ae5680d219aaf0428ff27ef744e814 Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Thu, 20 May 2021 14:25:41 +0100 Subject: [PATCH 702/760] added interleave functionality to make transform.js more readable --- .../x-teaser-timeline/src/lib/transform.js | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index b297a9295..bc88d8d98 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -149,6 +149,23 @@ const getGroupAndIndex = (groups, position) => { } } +const interleaveAllSlotsWithCustomSlots = ( + customSlotContentArray, + customSlotPositionArray, + itemGroups, + items +) => { + for (const [index, slotContent] of customSlotContentArray.entries()) { + const insertPosition = Math.min(customSlotPositionArray[index], items.length + index) + const insert = getGroupAndIndex(itemGroups, insertPosition) + const copyOfItems = [...itemGroups[insert.group].items] + + copyOfItems.splice(insert.index, 0, slotContent) + itemGroups[insert.group].items = copyOfItems + return itemGroups + } +} + export const buildModel = ({ items, customSlotContent, @@ -158,28 +175,26 @@ export const buildModel = ({ latestItemsTime, latestItemsAgeHours }) => { - const itemGroups = getItemGroups({ + let itemGroups = getItemGroups({ items, timezoneOffset, localTodayDate, latestItemsTime, latestItemsAgeHours }) + if (itemGroups.length > 0 && customSlotContent) { const customSlotContentArray = Array.isArray(customSlotContent) ? customSlotContent : [customSlotContent] const customSlotPositionArray = Array.isArray(customSlotPosition) ? customSlotPosition : [customSlotPosition] - for (const [index, slotContent] of customSlotContentArray.entries()) { - const insertPosition = Math.min(customSlotPositionArray[index], items.length + index) - const insert = getGroupAndIndex(itemGroups, insertPosition) - const copyOfItems = [...itemGroups[insert.group].items] - - copyOfItems.splice(insert.index, 0, slotContent) - - itemGroups[insert.group].items = copyOfItems - } + itemGroups = interleaveAllSlotsWithCustomSlots( + customSlotContentArray, + customSlotPositionArray, + itemGroups, + items + ) } return itemGroups } From c787c5e8ec710f6203cd4b83f9ba9c2bc92e7d07 Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Thu, 20 May 2021 14:50:20 +0100 Subject: [PATCH 703/760] fixed interleave function --- components/x-teaser-timeline/src/lib/transform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser-timeline/src/lib/transform.js b/components/x-teaser-timeline/src/lib/transform.js index bc88d8d98..cb6cd3c3c 100644 --- a/components/x-teaser-timeline/src/lib/transform.js +++ b/components/x-teaser-timeline/src/lib/transform.js @@ -162,8 +162,8 @@ const interleaveAllSlotsWithCustomSlots = ( copyOfItems.splice(insert.index, 0, slotContent) itemGroups[insert.group].items = copyOfItems - return itemGroups } + return itemGroups } export const buildModel = ({ From 4ea23f0546ac493ed9743101d1ea2c85aac8ce8c Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Thu, 20 May 2021 16:06:26 +0100 Subject: [PATCH 704/760] additional test case added for non-zero position --- .../__tests__/lib/transform.test.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/components/x-teaser-timeline/__tests__/lib/transform.test.js b/components/x-teaser-timeline/__tests__/lib/transform.test.js index f3f2a5c30..813b13be0 100644 --- a/components/x-teaser-timeline/__tests__/lib/transform.test.js +++ b/components/x-teaser-timeline/__tests__/lib/transform.test.js @@ -322,6 +322,30 @@ describe('buildModel', () => { groupedItems[2] ]) }) + test('returns correct model for a non-zero custom slot', () => { + const result = buildModel({ + items, + timezoneOffset: 0, + localTodayDate: '2020-01-14', + customSlotContent: [{ foo: 1 }, { bar: 2 }], + customSlotPosition: [2, 3] + }) + expect(result).toEqual([ + { + ...groupedItems[0], + items: [ + groupedItems[0].items[0], + groupedItems[0].items[1], + { foo: 1 }, + groupedItems[0].items[2], + { bar: 2 }, + groupedItems[0].items[3] + ] + }, + groupedItems[1], + groupedItems[2] + ]) + }) test('returns correct model for multiple custom slots off end of all groups', () => { const result = buildModel({ items, From ba495c198b18f005d6f0ee859ff8b79aef65031c Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Wed, 9 Jun 2021 13:50:19 +0100 Subject: [PATCH 705/760] config to deploy storybook --- .circleci/config.yml | 28 +++++----------------------- package.json | 14 ++++++++++---- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e1269872c..88b29563b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,10 +24,6 @@ references: keys: - cache-root-v4-{{ .Branch }}-{{ checksum "./package.json" }} - cache_keys_docs: &cache_keys_docs - keys: - - cache-docs-v4-{{ .Branch }}-{{ checksum "./web/package.json" }} - # # Cache creation # @@ -37,12 +33,6 @@ references: paths: - ./node_modules/ - create_cache_docs: &create_cache_docs - save_cache: - key: cache-docs-v4-{{ .Branch }}-{{ checksum "./web/package.json" }} - paths: - - ./web/node_modules/ - # # Cache restoration # @@ -116,6 +106,9 @@ jobs: - run: name: Run tests command: make test + - run: + name: Run storybook + command: npm run storybook:ci publish: <<: *container_config_node @@ -159,20 +152,9 @@ jobs: <<: *container_config_node steps: - *attach_workspace - - add_ssh_keys: - fingerprints: - - "2b:98:17:21:34:bf:5d:3b:15:a5:82:77:90:11:03:e9" - - *restore_cache_docs - - run: - name: Install documentation website dependencies - command: npm run install-docs - - *create_cache_docs - - run: - name: Build documentation website - command: npm run build-docs - run: - name: shared-helper / publish-github-pages - command: .circleci/shared-helpers/helper-publish-github-pages + name: Deploy Storybook + command: npm run deploy-storybook:ci workflows: diff --git a/package.json b/package.json index a4ffc2c78..2231d7ab0 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "name": "x-dash", "private": true, "scripts": { "clean": "git clean -fxdi", @@ -9,8 +10,7 @@ "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", - "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", - "install-docs": "(cd web && npm install)", + "deploy-storybook:ci": "storybook-to-ghpages --ci --source-branch=main", "start-docs": "(cd web && npm start)", "build-docs": "(cd web && npm run build)", "heroku-postbuild": "make install && npm run build", @@ -22,11 +22,17 @@ "eslint --fix" ] }, + "storybook-deployer": { + "gitUsername": "next-team", + "gitEmail": "next.team@ft.com", + "commitMessage": "Deploy Storybook [skip ci]" + }, "devDependencies": { "@babel/core": "^7.4.5", "@financial-times/athloi": "^1.0.0-beta.26", - "@storybook/addon-essentials": "^6.1.8", - "@storybook/react": "^6.1.8", + "@storybook/addon-essentials": "^6.2.9", + "@storybook/react": "^6.2.9", + "@storybook/storybook-deployer": "^2.8.8", "@types/jest": "26.0.0", "@typescript-eslint/parser": "^3.0.0", "babel-loader": "^8.0.4", From ab96e4698fed7358acdecdb7d757a7916b976da6 Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Wed, 9 Jun 2021 13:51:05 +0100 Subject: [PATCH 706/760] move images to folder for wiki --- .../x-privacy-manager/docs => wiki_images}/ccpa.png | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename {components/x-privacy-manager/docs => wiki_images}/ccpa.png (100%) diff --git a/components/x-privacy-manager/docs/ccpa.png b/wiki_images/ccpa.png similarity index 100% rename from components/x-privacy-manager/docs/ccpa.png rename to wiki_images/ccpa.png From 4e9e77fe493f954dd86253e1c458328d1fe23c7e Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Wed, 9 Jun 2021 13:52:23 +0100 Subject: [PATCH 707/760] delete gatsby docs --- docs/components/creation.md | 27 - docs/components/interactions.md | 3 - docs/components/javascript.md | 78 -- docs/components/overview.md | 61 -- docs/components/release-guidelines.md | 26 - docs/components/stories.md | 102 --- docs/components/styling.md | 86 --- docs/components/testing.md | 45 -- docs/get-started/installation.md | 43 -- docs/get-started/what-is-x-dash.md | 44 -- docs/get-started/working-with-x-dash.md | 72 -- docs/guides/migrating-to-x-teaser.md | 148 ---- docs/index.md | 1 - docs/integration/local-development.md | 58 -- docs/integration/using-components.md | 99 --- packages/x-logo/package.json | 17 - packages/x-logo/src/color.js | 20 - packages/x-logo/src/index.js | 98 --- packages/x-logo/src/noise.js | 14 - packages/x-logo/src/polygon.js | 20 - packages/x-logo/src/triangles.js | 6 - packages/x-logo/src/util.js | 3 - web/.gitignore | 3 - web/.npmrc | 1 - web/gatsby-config.js | 54 -- web/gatsby-node.js | 11 - web/package.json | 39 - .../extend-node-type.js | 13 - .../gatsby-node.js | 3 - .../on-create-node.js | 32 - .../package.json | 3 - web/src/components/footer/index.jsx | 38 - web/src/components/header/index.jsx | 22 - web/src/components/icon/index.jsx | 10 - web/src/components/layouts/basic.jsx | 18 - web/src/components/layouts/splash.jsx | 19 - web/src/components/module-list/index.jsx | 15 - web/src/components/sidebar/module-menu.jsx | 17 - web/src/components/sidebar/pages-menu.jsx | 25 - web/src/components/story-viewer/index.jsx | 22 - web/src/components/tertiary/links.jsx | 32 - web/src/components/tertiary/subheadings.jsx | 57 -- web/src/data/docs-menu.yml | 39 - web/src/html.jsx | 26 - web/src/lib/create-documentation-pages.js | 57 -- web/src/lib/create-npm-package-pages.js | 60 -- web/src/lib/decorate-nodes.js | 33 - web/src/pages/components.jsx | 31 - web/src/pages/index.jsx | 66 -- web/src/pages/packages.jsx | 31 - web/src/templates/documentation-page.jsx | 45 -- web/src/templates/npm-package.jsx | 55 -- web/static/favicon.ico | Bin 6518 -> 0 bytes web/static/logo.png | Bin 4378 -> 0 bytes web/static/main.css | 714 ------------------ web/static/prism.css | 57 -- 56 files changed, 2719 deletions(-) delete mode 100644 docs/components/creation.md delete mode 100644 docs/components/interactions.md delete mode 100644 docs/components/javascript.md delete mode 100644 docs/components/overview.md delete mode 100644 docs/components/release-guidelines.md delete mode 100644 docs/components/stories.md delete mode 100644 docs/components/styling.md delete mode 100644 docs/components/testing.md delete mode 100644 docs/get-started/installation.md delete mode 100644 docs/get-started/what-is-x-dash.md delete mode 100644 docs/get-started/working-with-x-dash.md delete mode 100644 docs/guides/migrating-to-x-teaser.md delete mode 100644 docs/index.md delete mode 100644 docs/integration/local-development.md delete mode 100644 docs/integration/using-components.md delete mode 100644 packages/x-logo/package.json delete mode 100644 packages/x-logo/src/color.js delete mode 100644 packages/x-logo/src/index.js delete mode 100644 packages/x-logo/src/noise.js delete mode 100644 packages/x-logo/src/polygon.js delete mode 100644 packages/x-logo/src/triangles.js delete mode 100644 packages/x-logo/src/util.js delete mode 100644 web/.gitignore delete mode 100644 web/.npmrc delete mode 100644 web/gatsby-config.js delete mode 100644 web/gatsby-node.js delete mode 100644 web/package.json delete mode 100644 web/plugins/gatsby-transformer-npm-package/extend-node-type.js delete mode 100644 web/plugins/gatsby-transformer-npm-package/gatsby-node.js delete mode 100644 web/plugins/gatsby-transformer-npm-package/on-create-node.js delete mode 100644 web/plugins/gatsby-transformer-npm-package/package.json delete mode 100644 web/src/components/footer/index.jsx delete mode 100644 web/src/components/header/index.jsx delete mode 100644 web/src/components/icon/index.jsx delete mode 100644 web/src/components/layouts/basic.jsx delete mode 100644 web/src/components/layouts/splash.jsx delete mode 100644 web/src/components/module-list/index.jsx delete mode 100644 web/src/components/sidebar/module-menu.jsx delete mode 100644 web/src/components/sidebar/pages-menu.jsx delete mode 100644 web/src/components/story-viewer/index.jsx delete mode 100644 web/src/components/tertiary/links.jsx delete mode 100644 web/src/components/tertiary/subheadings.jsx delete mode 100644 web/src/data/docs-menu.yml delete mode 100644 web/src/html.jsx delete mode 100644 web/src/lib/create-documentation-pages.js delete mode 100644 web/src/lib/create-npm-package-pages.js delete mode 100644 web/src/lib/decorate-nodes.js delete mode 100644 web/src/pages/components.jsx delete mode 100644 web/src/pages/index.jsx delete mode 100644 web/src/pages/packages.jsx delete mode 100644 web/src/templates/documentation-page.jsx delete mode 100644 web/src/templates/npm-package.jsx delete mode 100644 web/static/favicon.ico delete mode 100644 web/static/logo.png delete mode 100644 web/static/main.css delete mode 100644 web/static/prism.css diff --git a/docs/components/creation.md b/docs/components/creation.md deleted file mode 100644 index e704d5322..000000000 --- a/docs/components/creation.md +++ /dev/null @@ -1,27 +0,0 @@ -# Creating components - -To create a new component, you can start by running the `blueprint` script and providing a component name. This script will initialise a skeleton component with the required files including a readme, package manifest and basic source files. - -You can run the blueprint script from the repository root like this: - -```sh -npm run blueprint -- denshiba -``` - -_Please note: You do not need to prefix the component name with `x-`._ - -When the blueprint script runs it will initialise a new component with the following file structure: - -```sh -├ src/ -│ └ Denshiba.jsx -├ stories/ -│ ├ story.js -│ └ index.js -├ .npmignore -├ package.json -├ readme.md -└ rollup.js -``` - -All JavaScript and CSS source files should be stored in the `src` directory, Storybook configuration for the component must be maintained in the `stories` directory. Every package in x-dash must have a readme and a package manifest and public packages must include a `.npmignore` file. diff --git a/docs/components/interactions.md b/docs/components/interactions.md deleted file mode 100644 index 275568e00..000000000 --- a/docs/components/interactions.md +++ /dev/null @@ -1,3 +0,0 @@ -# Component interactivity - -👷‍♀️ TODO 👷‍♂️ diff --git a/docs/components/javascript.md b/docs/components/javascript.md deleted file mode 100644 index 712efca7d..000000000 --- a/docs/components/javascript.md +++ /dev/null @@ -1,78 +0,0 @@ -# Component JavaScript - - -## Architecture - -Each component is a function which accepts an object containing all of the properties needed to render it and returns information describing what to render. These are called "functional components" or "stateless functional components" (SFCs) by many frameworks. - -Following this pattern means that components authored with x-dash can be compatible with a variety of static and dynamic runtimes including React, Preact, Inferno, VDO, and Hyperons amongst others. - -Interactive components which change state in reaction to events can still be created using the [x-interaction] component. - -[x-interaction]: /components/x-interaction - - -## Syntax - -You may use all syntax up to and including the ECMAScript 2018 specification and any features that may be polyfilled by the [Polyfill Service's default set] in your component source code (this assumes that all FT applications use the service). We do not currently support transpiling any features at proposal, draft, or candidate stages (if you are unsure what this means check out the [TC39 Process document]). - -If you are unsure about what you can or cannot use please check the [Can I Use] website or the [ECMAScript compatibility table]. - -All source code for your components must be authored as [ES Modules] using the `import` and `export` statements so that x-dash can generate and distribute optimised bundles. - -[Babel] is used to transpile component source code and [Rollup] is used to generate the bundles to be consumed by applications. - -[TC39 Process document]: https://tc39.github.io/process-document/ -[ES Modules]: https://ponyfoo.com/articles/es6-modules-in-depth -[Polyfill Service's default set]: https://polyfill.io/v2/docs/features/#default-sets -[Can I Use]: https://caniuse.com/ -[ECMAScript compatibility table]: https://kangax.github.io/compat-table/es6/ -[Babel]: https://babeljs.io/ -[Rollup]: https://rollupjs.org/ - - -## JSX - -Components are written using JSX (if you are not familiar with JSX check out [WTF is JSX] first) which provides special syntax for describing elements and composing components. JSX is an extension of JavaScript providing syntactic sugar to provide both a visual aid and to save writing lengthy function calls. It is implemented by the majority of JavaScript parsers and transpilers. - -_Please note: Files containing JSX should use the `.jsx` extension._ - -[WTF is JSX]:https://jasonformat.com/wtf-is-jsx/ - - -## Source files - -Each component and subcomponent should be authored in a separate file with the name of the component in `PascalCase` as the filename and using a `.jsx` extension, for example a component which shows the publish date of a piece of content may be named `PublishDate.jsx`. - -The `main` entry point of the component package should be the main component file itself, which should be named in the same format as all component files, instead of `index.js` or similar. It should export the main component as a named export, not `default`. - -If a component is made up of several subcomponents, it should export the subcomponents used to assemble the main component as named exports alongside the main component. - - -## Manifest - -There are three separate bundles which will be generated by x-dash for each component to suit different use cases. These map to several properties in the package manifest to provide these options to the component's consumers. - -- `main` - an ES2015 commonJS module for use by the server -- `module` - an ES2015 ES module for use by modern browsers -- `browser` - an ES5 commonJS module for use by older browsers - - -## Example - -Below shows the source code for a fictional x-content-item component. - -```jsx -// ContentItem.jsx -import PublishDate from './PublishDate'; - -const ContentItem = (props) => ( - <article> - <h2>{props.title}</h2> - {props.subtitle ? <p>{props.subtitle}</p> : null} - <PublishDate {...props} /> - </article> -); - -export { PublishDate, ContentItem }; -``` diff --git a/docs/components/overview.md b/docs/components/overview.md deleted file mode 100644 index f6752ff12..000000000 --- a/docs/components/overview.md +++ /dev/null @@ -1,61 +0,0 @@ -# Component overview - - -## Structure - -Components should follow the structure outlined below. JavaScript and CSS source files should be stored in the `src` directory, Storybook configuration for the component in the `stories` directory and the root of the component must include an [`.npmignore`][ignore] file, the package manifest, a readme, and the Rollup configuration. - -```sh -├ src/ -│ ├ styles.scss -│ └ Denshiba.jsx -├ stories/ -│ ├ story.js -│ └ index.js -├ .npmignore -├ package.json -├ readme.md -└ rollup.js -``` - -_Please note: Files containing JSX should use the `.jsx` extension._ - -[ignore]: https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package - - -## Compatibility - -Components written with x-dash are intended to work across varying environments so they must at least meet the FT's browser support requirements. Components should also be able to render on both the server and in the browser which means they must avoid or carefully wrap browser-specific or server-specific code. - -For specific information about compatibility see the [JavaScript] and [CSS] pages. - -[JavaScript]: /docs/components/javascript -[CSS]: /docs/components/styling - - -## Dependencies - -Components should have as few dependencies as possible and when using external dependencies you should always carefully consider the compatibility, file size, and future supportability of them. Where possible try to use dependencies which are already in common use across FT projects to avoid the need for applications to bundle multiple dependencies which provide similar functionality. - -_Please note: External dependencies will not be bundled with your source code._ - - -## Testing - -Tests are run across the whole project from the top level of the repository using the command `make test`. The test runner is [Jest] and [Enzyme] has been made available via the x-test-utils package for writing assertions against interactive components. - -Snapshot tests will be automatically created for each story that is configured for a component. These will fail should the output of the component for that story change. - -In addition it is encouraged to write unit tests for interactive or complex components. See the guide to [testing x-dash components] for more information. - -[Jest]: https://jestjs.io/ -[Enzyme]: http://airbnb.io/enzyme/ -[testing x-dash components]: /docs/components/testing - - -## Publishing - -All x-dash components and packages will be published on the [npm registry] under the `@financial-times` organisation. Components in the `main` or current `development` branches of x-dash will all be published concurrently with the same version number. Experimental components may be published with an unstable version number using a [prerelease tag]. - -[npm registry]: https://www.npmjs.com/ -[prerelease tag]: ./release-guidelines.md diff --git a/docs/components/release-guidelines.md b/docs/components/release-guidelines.md deleted file mode 100644 index 5255139b2..000000000 --- a/docs/components/release-guidelines.md +++ /dev/null @@ -1,26 +0,0 @@ -# Release Guidelines - -## Experimental features - -Only stable, well tested components and packages may be present in the main or development branches. _Any publishable code in the main or development branches must have been tested in both The App and FT.com_. This is so we do not release unproven components with a stable version number. - -To develop your component create a new feature branch including your module name, for example if you are building a new tabs component you would create a branch named `feature-x-tabs`. Your component will stay in this branch until it is ready to be merged into the next major or minor release so you are encouraged to merge from or rebase onto the latest development or main branch regularly. You are welcome to raise pull requests against your feature branch if you need to. - -Because experimental modules will not be included in any stable releases we allow them to be published separately using a pre-1.0.0 version number. You are free to make as many prereleases as you need. To create a prerelease of your experimental module you must create a tag in the format `module-name-v0.x.x`, for example to release the tabs component you would create tag named `x-tabs-v0.0.1` for the latest commit in the `feature-x-tabs` branch. - -You are encouraged to use an identifier to namespace your prereleases, e.g. `x-tags-v0.0.1-beta.1`, as this will also prevent Renovate from automatically creating a PR for updating applications using an earlier version of your component (this would be undesirable if your new component version contained breaking changes which cannot be expressed with semver). - -When your new module is considered stable raise a pull request against the current development branch. Your module will be released as part of the next major or minor version. - - -## Releasing/Versioning - -All of our projects are versioned using [Semantic Versioning], you should familiarise yourself with this. The following guide will outline how to tag and release a new version of all projects, it assumes that all the code you wish to release is now on the `main` branch. - - 1. **Review the commits since the last release**. You can find the last release in the git log, or by using the compare feature on GitHub. Make sure you've pulled all of the latest changes. - - 2. **Decide on a version**. Work out whether this release is major, minor, or patch level. Major releases are generally planned out; if a breaking change has snuck into `main` without prior-planning it may be worth removing it or attempting to make it backwards-compatible. - - 3. **Add a release**. Create a release using the GitHub UI (note there should be a "v" preceeding the version number). This will automatically kick off a new build and publish each package. - -[semantic versioning]: http://semver.org/ diff --git a/docs/components/stories.md b/docs/components/stories.md deleted file mode 100644 index 40097416b..000000000 --- a/docs/components/stories.md +++ /dev/null @@ -1,102 +0,0 @@ -# Component stories - -## Setup - -TODO - -## Component configuration - -The Storybook configuration is a module which exports properties about the component to be rendered. - -Property | Description | Required ----------------|-----------------------------------------------------------|---------- -`component` | A reference to the top-level function to render | Yes -`package` | The component's package manifest | Yes -`dependencies` | An object containing the Origami dependencies to load | No -`stories` | An array of story configuration modules | Yes -`knobs` | A module generating dynamically editable knobs | No - -Here is an example component configuration: - -```js -const { Denshiba } = require('../'); - -// Reference to top-level function to render -exports.component = Denshiba; - -// The component's package manifest -exports.package = require('../package.json'); - -// Origami dependencies to load (from the Build Service) -exports.dependencies = { - 'o-normalise': '^1.6.0', - 'o-typography': '^5.5.0' -}; - -// Story configuration modules -exports.stories = [ - require('./food') -]; - -// A module generating dynamically editable knobs -exports.knobs = require('./knobs'); -``` - - -## Story configuration - -Story modules export the configuration required for each component demo. - -| Property | Description | Required | -|-------------|--------------------------------------------------------------------------------------------------------------------------------|----------| -| `title` | The title of the story | Yes | -| `data` | The data to pass as props to the component | Yes | -| `knobs` | An array of data properties to convert to interactive knobs | No | -| `fetchMock` | A function expecting an instance of [fetch-mock]. If your story makes HTTP requests, you can use this to mock their behaviour. | No | - -Here is an example story configuration: - -```js -// Title of the story -exports.title = 'Favourite food'; - -// Data to pass as props to the component -exports.data = { - question: 'What is your favourite food?', - answer: 'Sushi. Like news, I like my food fresh.' -}; - -// Data properties to convert to interactive knobs -exports.knobs = [ - 'question' - 'answer' -]; - -// A function expecting a clean instance of fetch-mock -exports.fetchMock = fetchMock => { - fetchMock.mock('/api/data', { some: 'data' }); -} -``` - -[fetch-mock]: https://www.wheresrhys.co.uk/fetch-mock - -## Knobs configuration - -Knobs wrap the data properties for a story and allow users to dynamically edit them in the UI. It is a function which receives the story data and functions to create different types of knob. See the [Storybook knobs add-on] for more information. - -Here is an example knobs configuration: - -```js -module.exports = (data, createKnob) => { - return { - question() { - return createKnob.text('Question', data.question); - }, - answer() { - return createKnob.text('Answer', data.answer); - } - }; -}; -``` - -[Storybook knobs add-on]: https://github.com/storybooks/storybook/tree/HEAD/addons/knobs diff --git a/docs/components/styling.md b/docs/components/styling.md deleted file mode 100644 index 21f3f694d..000000000 --- a/docs/components/styling.md +++ /dev/null @@ -1,86 +0,0 @@ -# Styling components - - -## Origami components - -Components which provide markup and logic for existing Origami components do not need to provide any styles and should instead provide instructions for [installing the Origami package] in their readme. These components should expect the required class names to be made available globally by the consuming application. - -To include Origami styles in a component's Storybook demos use the `dependencies` option in your component's [Storybook configuration]. - -[installing the Origami package]: https://origami.ft.com/docs/developer-guide/modules/building-modules/#4-set-up-a-package-manifest-to-load-origami-modules -[Storybook configuration]: /docs/components/stories - - -## Component-specific styles - -For components which are not a part of Origami, x-dash provides the tools to author and bundle styles alongside the JavaScript package. Styles can be written using [Sass] and it is strongly encouraged to follow the [Origami SCSS syntax standard] which in short means: - -- _Always_ import Origami dependencies in silent mode and integrate via mixins -- Never modify selectors from another component's namespace -- Do not use ID selectors, `:not()`, and avoid `%extends` -- Use ARIA role attribute selectors where appropriate - -CSS files should be stored in `src` directory, adjacent to the component that will use them and follow the same naming convention. For example, if you have a button component then you may have two files; `Button.jsx` and `Button.css`. - -If your component's styles might be useful outside of the FT.com ecosystem, you should speak to the [Origami team] about creating an Origami component. - -[Sass]: https://sass-lang.com/ -[Origami SCSS syntax standard]: https://origami.ft.com/docs/syntax/scss/ -[Origami team]: http://origami.ft.com/ - - -### CSS Modules - -A [CSS Module] is a self-contained CSS file that can be used with ECMAScript `import`. When a component is built, its CSS Modules are bundled into a single `.css` file, and their class names are obfuscated, so styles from a component cannot interfere with any other part of a page they're included into. The `default` export of a CSS Module is an object containing its class names as keys, and their obfuscated versions as values. This allows you to reference the class names as authored from within your component but output the obfuscated names when the component is used. - - -### Example - -The following CSS defines class names which are short and generic but there is no risk of them interfering with anything else on the page because they'll be obfuscated. Class names should be written in camelCase so they may be more easily referenced in JavaScript. - -```css -/* Button.css */ -.button { - background: steelblue; - color: white; - border-radius: 0.25em; - padding: 0.5rem 1rem; -} - -.large { - font-size: 1.5rem; -} - -.danger { - background: firebrick; -} -``` - -When this CSS file is imported its selectors can be referenced using the object it exports: - -```jsx -// Button.jsx -import { h } from '@financial-times/x-engine'; -import styles from './Button.css'; - -const Button = ({ large, danger }) => { - const classNames = [ - styles.button, - large ? styles.large : '', - danger ? styles.danger : '' - ]; - - return <button className={classNames.join(' ')}>Click me!</button>; -}; -``` - -_Please note: referencing classes and toggling them based on properties can become unwieldy so the [classnames] or [classcat] packages can help avoid some of the formatting hassle._ - -[CSS Module]: https://github.com/css-modules/css-modules -[classnames]: https://npmjs.org/package/classnames -[classcat]: https://github.com/jorgebucaran/classcat - - -### Manifest - -To instruct the x-dash tools where to output styles and provide a hint to consumers where to import styles from, add a `style` property to the component's package manifest. This is the path to the bundled CSS output. diff --git a/docs/components/testing.md b/docs/components/testing.md deleted file mode 100644 index 1223454f8..000000000 --- a/docs/components/testing.md +++ /dev/null @@ -1,45 +0,0 @@ -# Testing components - -Defining [stories] from your components, as well as including them in Storybook, will generate Jest snapshot tests for each story. Your stories should include as many of your component's use cases as possible, so that they're covered by the tests. - -Snapshot tests are useful for ensuring that you don't accidentally change the markup that your component outputs, which may inadvertently break the apps consuming your component. They don't cover any kind of interactive behaviour or changing state. For components using `x-interaction`, you should consider adding test cases for the interactivity. - -[stories]: /docs/components/stories - -## Interactive components - -x-dash comes with Jest and Enzyme pre-configured, to help save boilerplate and setup when testing your component. - -For an example of Jest and Enzyme in use, and standard patterns for writing tests for a component, see the [x-increment tests](https://github.com/Financial-Times/x-dash/tree/HEAD/components/x-increment/__tests__). - -### Setup - -Install `x-test-utils` as a `devDependency` of your component: - -``` -npm install --save-dev ../../packages/x-test-utils -``` - -This module exports a version of Enzyme pre-configured to work with Jest and the version of React in use. Create a folder called `__tests__` in your component's folder. Test files should be placed in this folder, using the filename extension `.test.jsx`. It's up to you how to structure your tests; for simple tests we recommend a single file named after your component, e.g. `x-increment.test.jsx`. - -The standard pattern for testing an interactive component is rendering it using Enzyme's `mount` DOM renderer, using its selector functions to select an element that would trigger an action, and directly calling the event that calls the action. `x-interaction` actions return a promise that resolves once the state changes have taken place, so you should `await` this promise, then use Jest's `expect` to assert that the DOM changes you expect have take place. - -For example, the following is an annotated excerpt from `x-increment`'s tests: - -```jsx -const { h } = require('@financial-times/x-engine'); -const { mount } = require('@financial-times/x-test-utils/enzyme'); - -const { Increment } = require('../'); - -describe('x-increment', () => { - it('should increment when clicked', async () => { - // render the component with the DOM renderer - const subject = mount(<Increment count={1} />); - // find the button that triggers the increment, and call its `onClick` event. `await` state updates - await subject.find('button').prop('onClick')(); - // has the number in the text incremented? - expect(subject.find('span').text()).toEqual('2'); - }); -}); -``` diff --git a/docs/get-started/installation.md b/docs/get-started/installation.md deleted file mode 100644 index 0f479df0f..000000000 --- a/docs/get-started/installation.md +++ /dev/null @@ -1,43 +0,0 @@ -# Installing x-dash - - -## Requirements - -To get started with x-dash, you'll need to make sure you have the following software tools installed. - -1. [Git](https://git-scm.com/) -2. [Make](https://www.gnu.org/software/make/) -3. [Node.js](https://nodejs.org/en/) (version 12) -4. [npm](http://npmjs.com/) - -Please note that x-dash has only been tested in Mac and Linux environments. If you are on a Mac you may find it easiest to install the [Command Line Tools](https://developer.apple.com/download/more/) package which includes Git and Make. - -### Recommended - -To aid the development of interactive components with Storybook it is recommended to install the React Developer Tools for your browser. These addons aid debugging by displaying the rendered component tree and provide access to view and edit their properties: - -- [React Developer Tools for Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) -- [React Developer Tools for Firefox](https://addons.mozilla.org/en-GB/firefox/addon/react-devtools/) - - -## Project setup - -1. Clone the x-dash Git repository and change to the new directory that has been created: - - ```bash - git clone git@github.com:financial-times/x-dash - cd x-dash - ``` - -2. Install all of the project dependencies (this may take a few minutes if you are running this for the first time): - - ```bash - make install - ``` - -3. Build the current set of x-dash components and start Storybook to view: - - ```bash - make build - npm run start-storybook - ``` diff --git a/docs/get-started/what-is-x-dash.md b/docs/get-started/what-is-x-dash.md deleted file mode 100644 index 2ed81e2e4..000000000 --- a/docs/get-started/what-is-x-dash.md +++ /dev/null @@ -1,44 +0,0 @@ -# What is x-dash? - -This project is an experiment in building new shared UI components for FT.com and the FT App. An alternative introduction for FT developers is also available in [Google Slides] (viewable by employees only.) - -[Google Slides]: https://docs.google.com/presentation/d/1Z8mGsv4JU2TafNPIHw2RcejoNp7AN_v4LfCCGC7qrgw/edit?usp=sharing - - -## What are goals of this project? - -The primary goal is to create a set of UI components that are shareable between The App and FT.com. We aim to provide the patterns and tools required for developers to create high quality code which is suitable for use across both products. - -We want to do this to provide a consistent experience for our users who move between the two and act as an aid the technical evolution of both products. - - -## Why is this a challenge? - -Making components for both The App and FT.com is a technical challenge because the two products have different tech stacks, different architectures, and a different history. This means we must find a new middleground without introducing compromises or new technologies which might prevent teams from adopting it. - -This project is also a tricky cultural challenge because the two products have different life-cycles — the website ships many times a day, whereas an app may remain on a user's phone for several months — and not all existing components may have been originally conceived with both products in mind meaning we are unable to "lift and shift" them. - -And finally, both are enormous projects. Pivoting is not easy and takes a lot of effort. As a [free market] we can only convince teams to adopt x-dash by providing a truly better alternative to their current tools. - -[free market]: http://matt.chadburn.co.uk/notes/teams-as-services.html - - -## What is different about x-dash? - -With x-dash we have introduced a [monorepo] project structure, new [contribution guidelines], and a new [release process]. These have all been carefully considered to help ensure that x-dash components can be responsibly and reliably shipped to both products. - - -[monorepo]: https://en.wikipedia.org/wiki/Monorepo -[contribution guidelines]: https://github.com/Financial-Times/x-dash/blob/HEAD/contribution.md -[release process]: ../components/release-guidelines.md - - -## Does x-dash mean we can use React? - -This project does not require the use of any particular technology for your application. The aim of x-dash is to ensure all components are compatible with a variety of runtimes for both the server and the client-side, including [React]. - -By ensuring compatibility with React it is possible to take advantage of the fantastic tooling made available by that community, including [Storybook] and [Enzyme]. In addition, adopting these tools means that we are able to position our technology programmes more closely to the prevailing sentiment of the development community and therefore potential employees. - -[React]: https://reactjs.org/ -[Storybook]: https://storybook.js.org/ -[Enzyme]: https://github.com/airbnb/enzyme diff --git a/docs/get-started/working-with-x-dash.md b/docs/get-started/working-with-x-dash.md deleted file mode 100644 index 2ed585005..000000000 --- a/docs/get-started/working-with-x-dash.md +++ /dev/null @@ -1,72 +0,0 @@ -# Working with x-dash - -The project repository is a monorepo which means all of the tools, packages and components are kept in one place and can be worked upon concurrently. - - -## Repository structure - -The repository groups related code together in directories. UI components are stored in the `components` directory, documentation files in the `docs` directory, additional public packages in the `packages` directory and tools to aid working with x-dash are in the `tools` directory. - -``` -├ components/ -│ └ x-component/ -│ ├ readme.md -│ └ package.json -├ docs/ -│ └ page.md -├ packages/ -│ └ x-package/ -│ ├ readme.md -│ └ package.json -├ tools/ -│ └ x-docs/ -│ └ package.json -├ readme.md -└ package.json -``` - - -## Writing documentation - -The documentation you're reading right now is generated from a Markdown file stored in the `docs` directory. Other pages to show the packages and components are created dynamically using information inferred from the repository. - -This website is stored in the `web` directory and is built using the static site generator [Gatsby](https://gatsbyjs.org). You don't need to learn Gatsby to get started writing documentation! - -Once you have [installed] the x-dash project you can run this command from the repository root to install, build, and run the documentation website: - -```sh -npm run install-docs -npm run start-docs -``` - -Using this command the documentation site will be generated and start a server running on [local.ft.com:8000]. Whilst the server is running all of the files used as data sources will be watched and the website will automatically update when they change. - -[installed]: /docs/get-started/installation -[local.ft.com:8000]: http://local.ft.com:8000/ - - -## Using Storybook - -[Storybook] is a development environment and showcase for UI components. It makes working on and sharing UI components easier by providing a richly configurable environment. - -After installing x-dash you can start Storybook by running the following command from the repository root: - -```sh -npm run start-storybook -``` - -This command will start a server running on [local.ft.com:9001] and generate an application presenting all of the components configured to use it. Changes to these components can be updated in real-time speeding up the development process. - -Data properties passed to these component may also be configured in-situ and these changes will be reflected in the URL making it possible to share specific configurations. - -[Storybook]: https://storybook.js.org/ -[local.ft.com:9001]: http://local.ft.com:9001/ - - -## Coding standards - -The best way to ensure you stick to the x-dash code style is to make your work consistent with the code around it. We also provide a [Prettier] configuration to automatically format files and run [ESLint] before any tests. See the [contribution guide] for more information. - -[Prettier]: https://prettier.io/ -[ESLint]: https://eslint.org/ -[contribution guide]: https://github.com/Financial-Times/x-dash/blob/HEAD/contribution.md diff --git a/docs/guides/migrating-to-x-teaser.md b/docs/guides/migrating-to-x-teaser.md deleted file mode 100644 index 4d8472600..000000000 --- a/docs/guides/migrating-to-x-teaser.md +++ /dev/null @@ -1,148 +0,0 @@ -# Migrating from n-teaser to x-teaser - -This component replaces the existing `@financial-times/n-teaser` package. The design decisions behind x-teaser are different to n-teaser and so the available options and data structures required are also different. However, great care has been taken to ensure that migrating an app to use x-teaser can be done quickly and in most cases you can end up with less code than before. - - -## Major differences - -The n-teaser package provides a set of [Handlebars] templates for use by FT.com within the Handlebars setup provided by [n-ui] and several [GraphQL] fragments to fetch a range of data from [Next API]. Presenter classes are loaded on application startup and called from inside the templates, provising the logic to select and format data. The n-teaser package contains 78kb of source code. - -The x-teaser package provides a single configurable component written in [JSX] which may be rendered in The App or FT.com by any compatible runtime. The component expects the data it receives to be preformatted and therefore contains very little logic. The x-teaser package contains 18kb of source code. - -[Handlebars]: https://handlebarsjs.com/ -[n-ui]: https://github.com/Financial-Times/n-ui -[GraphQL]: https://graphql.org/ -[Next API]: https://github.com/Financial-Times/next-api -[JSX]: https://jasonformat.com/wtf-is-jsx/ - - -## Guide - -### 1. Install dependencies - -In addition to the x-teaser package you will also need to install the [x-handlebars] package which enables x-dash compatible components to be rendered inside your existing templates. - -```diff - "dependencies" { -- "@financial-times/n-teaser": "^4.10.0", -+ "@financial-times/x-handlebars": "^1.0.0", -+ "@financial-times/x-teaser": "^1.0.0", - }, -``` - -[x-handlebars]: /packages/x-handlebars - -### 2. Install and configure a runtime - -There are a number of frameworks and libraries which can render components written with JSX. If you are already using a framework in your application then you should continue to use that where possible, otherwise it is recommend installing the [Hyperons] package which is small and very fast. - -The x-handlebars and x-teaser packages depend on a library called [x-engine]. This is a consolidation library that provides your chosen runtime to x-dash compatible components. The configuration for x-engine needs to be added to your package manifest now to instruct it how to load your runtime. - -```diff - "dependencies" { -+ "hyperons": "^0.4.0", - }, -+ "x-dash": { -+ "engine": { -+ "server": "hyperons" -+ } -+ } -``` - -[x-engine]: /packages/x-engine -[Hyperons]: https://www.npmjs.com/package/hyperons - -### 3. Load Handlebars helpers - -User facing FT.com applications use an Express server provided by n-ui. As part of the server initialisation any Handlebars helpers can be loaded and made available to your templates. - -The n-teaser package uses this functionality to load its internal helper functions and the x-handlebars helpers are loaded in the same way. - -```diff - helpers: { -- nTeaserPresenter: require('@financial-times/n-teaser').presenter -+ x: require('@financial-times/x-handlebars')() - } -``` - -### 4. Fetching the right data - -The data required to render teasers can now be fetched in a format ready for immediate use. Whether your application fetches its data from Next API (using GraphQL queries) or directly from [Elasticsearch] the number of fields required and size of the payload to be transferred has been reduced dramatically. - -[Elasticsearch]: https://github.com/Financial-Times/next-es-interface/ - -#### Changes to GraphQL queries - -With GraphQL every field and sub-field to be retrieved must be explicitly specified. For convenience the n-teaser package provides a range of GraphQL fragments to generate this list of fields for each teaser type. - -To avoid maintaining such a list the teaser data has been made available as a single JSON field named `teaser`. - -```diff -- const { fragments } = require('@financial-times/n-teaser'); - -module.exports = ` -- ${fragments.teaserExtraLight} -- ${fragments.teaserLight} -- ${fragments.teaserStandard} -- ${fragments.teaserHeavy} -- ${fragments.teaserTopStory} -+ teaser -`; -``` - -#### Changes to Elasticsearch queries - -Wherever you specify a list, or include a list, of source fields to retrieve you may now replace this with a single entry of `teaser.*`. - -```diff -fields: [ -- 'id', -- 'url', -- 'relativeUrl', -- 'type', -- -- 'title', -- 'alternativeTitles.promotionalTitle', -- 'alternativeTitles.promotionalTitleVariant', -- -- 'standfirst', -- 'alternativeStandfirsts.promotionalStandfirst', -- 'alternativeStandfirsts.promotionalStandfirstVariant', -- -- 'publishedDate', -- 'firstPublishedDate', -- -- 'mainImage.*', -- 'alternativeImages.promotionalImage.*', - -- 'displayConcept.*', -- 'brandConcept.*', -- 'genreConcept.*', -- 'authorConcepts.*', -- -+ 'teaser.*' -]; -``` - -### 5. Update template includes - -The n-teaser package provides separate templates for each teaser layout. In contrast the x-handlebars package is generic and allows you to render any installed x-dash packages or local components in your Handlebars template. - -Teasers may be configured by providing attributes. Common use cases are provided via [presets] and may be used by specifying the `preset` attribute. - -```diff -- {{>n-teaser/templates/heavy mods=(array 'small') widths="[160]" }} -+ {{{x package="x-teaser" component="Teaser" preset="SmallHeavy"}}} -``` - -[presets]: /components/x-teaser#presets - -### 6. Image lazy loading (optional) - -If you have implemented image lazy loading on your pages using [o-lazy-load] you can continue to use this functionality with x-teaser. Setting the `imageLazyload` property to `true` will instruct the component to render the image with a `data-src` property instead of a `src` property. If you need to set a specific class name to identify these images you can set the `imageLazyLoad` property to a string, which will be appended to list of image class names. - -```handlebars -<!-- if using o-lazy-load --> -{{{x package="x-teaser" component="Teaser" preset="SmallHeavy" imageLazyLoad="o-lazy-load"}}} - -[o-lazy-load]: https://github.com/Financial-Times/o-lazy-load/ diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 0c4999684..000000000 --- a/docs/index.md +++ /dev/null @@ -1 +0,0 @@ -Learn about x-dash, whether you're getting started or want in-depth information, from the guides in the menu. diff --git a/docs/integration/local-development.md b/docs/integration/local-development.md deleted file mode 100644 index 2f69d5e7a..000000000 --- a/docs/integration/local-development.md +++ /dev/null @@ -1,58 +0,0 @@ -# Local development with x-dash - -When developing an x-dash component it is recommended to use Storybook but it can still be useful to see it in the context of a real app. - - -## Prerequisites - -This guide assumes that: - - - You have a ready to run Node.js application - - You have x-dash set up for development according to the [installation guide] - -The examples are based upon the following directory structure: - -``` -├ projects/ -│ ├ your-application/ -│ └ x-dash/ -``` - -[installation guide]: /docs/get-started/installation - - -## Installing local components - -As an example, we'll install the [x-teaser] component into an app. In the application folder run the `npm install` command using the relative path from the current directory to the required component: - -```sh -npm install --save ../x-dash/components/x-teaser -``` - -If your application previously specified the component as a dependency then you should see that the version specifier in the package manifest has now changed to the path that you specified prefixed with the `file:` protocol. The component should now have been installed into the `node_modules` directory as a symbolic link to within `x-dash`. - -Using the `file:` version specifier for subsequent runs of `npm install` will continue to work so long as the files are kept in the correct locations. Otherwise, npm will return this error: - -``` -npm ERR! code ENOLOCAL -npm ERR! Could not install from "../x-dash/components/x-teaser" as it does not contain a package.json file. -``` - -If you encounter this error, ensure that x-dash is set up correctly in the parent folder of your app, or reinstall `x-teaser` from the npm registry using `npm install --save @financial-times/x-teaser`. - -[x-teaser]: /components/x-teaser - - -## Avoid linking - -Usually, using a locally-installed version of a package is a use case for `npm link`. In practice, we have found it to be brittle, causing problems with peer dependencies and nested transitive dependencies. Using relative `npm install` treats the installed package as any other, ensuring that your `node_modules` directory has the expected structure. - -## Build one component only - -If you don't want to build all the components while you are working on one, there is a convenience `make` command that allows you to limit builds by keyword - -Use your package's name with the `build-` command on the command line - -```bash -make build-x-name-of-component -``` \ No newline at end of file diff --git a/docs/integration/using-components.md b/docs/integration/using-components.md deleted file mode 100644 index a5a3d0aa2..000000000 --- a/docs/integration/using-components.md +++ /dev/null @@ -1,99 +0,0 @@ -# Application setup - -Components authored with x-dash are authored using [JSX] and are designed to be compatible with a variety of runtimes to make integrating them into your application as flexible as possible. They can be used with any React-like library, such as Preact or Inferno, or rendered statically. They can also be integrated with Handlebars using the [x-handlebars package]. - -[JSX]: https://jasonformat.com/wtf-is-jsx/ -[x-handlebars package]: /packages/x-handlebars - - -## Choosing a runtime - -There are a number of JavaScript tools and frameworks which support JSX. The most well known of these is [React] and its derivitives including [Preact] and [Inferno]. These tools all implement a similar feature set for rendering interactive interfaces. There are also tools able to render JSX to static HTML such as [Hyperons] and [VDO] which may be a good choice when integrating x-dash components into an existing server-side rendered app. - -When building interactive interfaces for the client-side it is recommended to use Preact as this is already in use around the FT. For rendering static HTML on the server it is recommended to use Hyperons because it has excellent performance. - -[React]: https://reactjs.org/ -[Preact]: https://preactjs.com/ -[Inferno]: https://infernojs.org/ -[Hyperons]: https://github.com/i-like-robots/hyperons -[VDO]: https://www.npmjs.com/package/vdo - - -## Installing components - -Components should be installed and added to your application's dependencies using [npm]. For example, to install the [x-teaser component] you would run: - -``` -npm install --save @financial-times/x-teaser -``` - -_Please note: Remember that you will also need to install your chosen runtime!_ - -[npm]: https://npmjs.org -[x-teaser component]: /components/x-teaser - - -## Configuration - -Because x-dash components cannot interface with the runtime directly a consolidation library named [x-engine] is used to connect them. You must provide x-engine with some configuration added to your application's `package.json` to instruct it how to load the runtime. For example to use the Hyperons module to render x-dash components on the server you may add this: - -```json -{ - "x-dash": { - "engine": { - "server": "hyperons" - } - } -} -``` - -Please refer to the the [x-engine documentation] for further configuration information. - -[x-engine]: /packages/x-engine -[x-engine documentation]: /packages/x-engine - - -## Rendering - -JSX source code is transpiled to a set of function calls for each element. These function calls will usually output a framework specific representation of each element which builds up a data structure describing what to render. Whichever runtime you choose it will provide a method to transform this data structure into HTML. You must call this render method yourself in your application. - -For example, if you are using React you may render an x-dash component like this in your client-side code: - -```jsx -const React = require('react'); -const ReactDOM = require('react-dom'); -const { Teaser } = require('@financial-times/x-teaser'); - -ReactDOM.render( - <Teaser title='Hello world' />, - document.querySelector('#target') -); -``` - -You can also access the x-engine package directly in your own code which may be convenient if you think you may change the runtime in future. For example: - -```jsx -const { render } = require('@financial-times/x-engine'); -const { Teaser } = require('@financial-times/x-teaser'); - -// JSX syntax is sugar for function calls so it is not required! -const html = render(Teaser({ title: 'Hello World' }); -``` - - -### Use component presets - -Components which have many configigurable properties may expose a collection `presets` which are groups of ready configured properties for common use-cases. These can be mixed into your existing data properties to make your code less repetitive. - -For example the x-teaser component can be configured in many different ways and [includes several presets]: - -```js -const { Teaser, presets } = require('@financial-times/x-teaser'); - -Teaser({ - ...presets.Hero, - ...teaserData, -}); -``` - -[includes several presets]: /components/x-teaser#presets diff --git a/packages/x-logo/package.json b/packages/x-logo/package.json deleted file mode 100644 index 873b62752..000000000 --- a/packages/x-logo/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@financial-times/x-logo", - "version": "0.0.0", - "private": true, - "description": "", - "main": "src/index.js", - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "delaunator": "^4.0.0", - "hsluv": "^0.0.3", - "point-in-polygon": "^1.0.1", - "poisson-disk-sampling": "^1.0.2", - "seedrandom": "^3.0.0" - } -} diff --git a/packages/x-logo/src/color.js b/packages/x-logo/src/color.js deleted file mode 100644 index cede4ab00..000000000 --- a/packages/x-logo/src/color.js +++ /dev/null @@ -1,20 +0,0 @@ -import { hsluvToHex } from 'hsluv' - -export default (shift, random) => { - // Start a hue rotation (0-360 degrees) from a random position - const hue = random() * 360 - - // Make a starting HSL color - const hues = [hue, hue + shift, hue - shift, hue + shift * 2] - - // Return a function to generate a color for a given coordinate - return ([x, y]) => { - const [tl, tr, bl, br] = hues - const th = tl + (tr - tl) * (x / 100) - const bh = bl + (br - bl) * (x / 100) - const hue = th + (bh - th) * (y / 100) - - // <http://www.hsluv.org/> - return hsluvToHex([hue, 85 + 10 * random(), 45 + 10 * random()]) - } -} diff --git a/packages/x-logo/src/index.js b/packages/x-logo/src/index.js deleted file mode 100644 index 5e3313cbc..000000000 --- a/packages/x-logo/src/index.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react' -import seedrandom from 'seedrandom' -import createColor from './color' -import createNoise from './noise' -import createPolygon from './polygon' -import createTriangles from './triangles' -import { pointsToString } from './util' - -const options = { - seed: Math.random(), - density: 15, - thickness: 16, - hueShift: 45 -} - -// Create a random number generator -const random = seedrandom(options.seed) - -// Create the standard size X for use as the clip mask -const polygonPoints = createPolygon({ - x: 0, - y: 0, - width: 100, - height: 100, - thickness: options.thickness -}) - -// Create a larger X "canvas" to place points and shapes within -const polygonCanvas = createPolygon({ - x: -25, - y: -25, - width: 150, - height: 150, - thickness: options.thickness * 1.25 -}) - -const animations = ` - @keyframes logoHueRotate { - 0% { filter: hue-rotate(0); } - 100% { filter: hue-rotate(359.9deg); } - } - - @keyframes logoShimmer { - 0% { opacity: 1; } - 50% { opacity: 0.8; } - 100% { opacity: 1; } - } -` - -// Create random points within the given canvas and with the given seed -const noise = createNoise(options.density, polygonCanvas, random) - -// Join the random points to create a set of triangles -const triangles = createTriangles(noise) - -// Create a random color generator from the given hue and seed -const getColor = createColor(options.hueShift, random) - -// Create an array to iterate over to draw each triangle -const numberOfTriangles = Array.from(Array(triangles.length / 3).keys()) - -export default () => ( - <React.Fragment> - <style>{animations}</style> - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - style={{ - animation: 'logoHueRotate 30s linear infinite' - }}> - <clipPath id="logo-clip-path"> - <polygon points={pointsToString(polygonPoints)} /> - </clipPath> - - <g clipPath="url(#logo-clip-path)"> - {numberOfTriangles.map((i) => { - const points = [noise[triangles[i * 3]], noise[triangles[i * 3 + 1]], noise[triangles[i * 3 + 2]]] - - const color = getColor(noise[triangles[i * 3]]) - - return ( - <polygon - key={`triangle-${i}`} - fill={color} - stroke={color} - strokeWidth="0.1%" - strokeLinejoin="round" - points={pointsToString(points)} - style={{ - animation: `logoShimmer ${(random() * 10 + 5).toFixed(2)}s linear infinite` - }} - /> - ) - })} - </g> - </svg> - </React.Fragment> -) diff --git a/packages/x-logo/src/noise.js b/packages/x-logo/src/noise.js deleted file mode 100644 index c77caf971..000000000 --- a/packages/x-logo/src/noise.js +++ /dev/null @@ -1,14 +0,0 @@ -import Poisson from 'poisson-disk-sampling' -import pointInPolygon from 'point-in-polygon' - -export default (density, canvas, random) => { - // Poisson noise produces randomly distributed points that may not be too close to each other - // <https://en.wikipedia.org/wiki/Shot_noise> - const noise = new Poisson([150, 150], 100 / density, 100, 30, random) - - // Remove any points that do not fit within the given canvas - return noise - .fill() - .map(([x, y]) => [x - 25, y - 25]) - .filter((point) => pointInPolygon(point, canvas)) -} diff --git a/packages/x-logo/src/polygon.js b/packages/x-logo/src/polygon.js deleted file mode 100644 index 7bd4547b3..000000000 --- a/packages/x-logo/src/polygon.js +++ /dev/null @@ -1,20 +0,0 @@ -// Returns an array of points for an X shaped polygon -export default ({ x, y, width, height, thickness }) => { - const middleX = x + width / 2 - const middleY = y + height / 2 - - return [ - [x, y + thickness], - [middleX - thickness, middleY], - [x, y + height - thickness], - [x + thickness, y + height], - [middleX, middleY + thickness], - [x + width - thickness, y + height], - [x + width, y + height - thickness], - [middleX + thickness, middleY], - [x + width, y + thickness], - [x + width - thickness, y], - [middleX, middleY - thickness], - [x + thickness, y] - ] -} diff --git a/packages/x-logo/src/triangles.js b/packages/x-logo/src/triangles.js deleted file mode 100644 index e3e723f1f..000000000 --- a/packages/x-logo/src/triangles.js +++ /dev/null @@ -1,6 +0,0 @@ -import Delaunay from 'delaunator' - -export default (noise) => { - const { triangles } = Delaunay.from(noise) - return triangles -} diff --git a/packages/x-logo/src/util.js b/packages/x-logo/src/util.js deleted file mode 100644 index 97dc55e5a..000000000 --- a/packages/x-logo/src/util.js +++ /dev/null @@ -1,3 +0,0 @@ -// Stringifies a list points -// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/points> -export const pointsToString = (points) => points.map((point) => point.map((x) => Math.round(x)).join()).join() diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index d5f088f78..000000000 --- a/web/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.cache -/public -/static/storybook diff --git a/web/.npmrc b/web/.npmrc deleted file mode 100644 index 43c97e719..000000000 --- a/web/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/web/gatsby-config.js b/web/gatsby-config.js deleted file mode 100644 index a600acd55..000000000 --- a/web/gatsby-config.js +++ /dev/null @@ -1,54 +0,0 @@ -module.exports = { - // The GitHub Pages deployment will be in this sub-folder - pathPrefix: '/x-dash', - polyfill: false, - siteMetadata: { - title: 'x-dash', - siteUrl: 'https://financial-times.github.io/x-dash/', - description: 'Shared front-end for FT.com and The App.' - }, - plugins: [ - { - resolve: 'gatsby-source-filesystem', - options: { - name: 'docs', - path: './src/data' - } - }, - { - resolve: 'gatsby-source-filesystem', - options: { - name: 'docs', - path: '../docs' - } - }, - { - resolve: 'gatsby-source-filesystem', - options: { - name: 'components', - path: '../components', - // Don't attempt to load any Storybook or source files, as these may - // contain syntax and/or features we cannot parse. - ignore: [/stories/, /storybook/, /src/] - } - }, - { - resolve: 'gatsby-source-filesystem', - options: { - name: 'packages', - path: '../packages' - } - }, - // Handles markdown files (creates "MarkdownRemark" nodes) - { - resolve: 'gatsby-transformer-remark', - options: { - plugins: ['gatsby-remark-prismjs', 'gatsby-remark-autolink-headers', 'gatsby-remark-external-links'] - } - }, - // Handles package manifest files (creates "NpmPackage" nodes) - 'gatsby-transformer-npm-package', - // Handles YAML files (creates "YourFileNameYaml" nodes) - 'gatsby-transformer-yaml' - ] -} diff --git a/web/gatsby-node.js b/web/gatsby-node.js deleted file mode 100644 index ce52d061d..000000000 --- a/web/gatsby-node.js +++ /dev/null @@ -1,11 +0,0 @@ -const decorateNodes = require('./src/lib/decorate-nodes') -const createNpmPackagePages = require('./src/lib/create-npm-package-pages') -const createDocumentationPages = require('./src/lib/create-documentation-pages') - -exports.onCreateNode = ({ node, actions, getNode }) => { - decorateNodes(node, actions, getNode) -} - -exports.createPages = async ({ actions, graphql }) => { - return Promise.all([createNpmPackagePages(actions, graphql), createDocumentationPages(actions, graphql)]) -} diff --git a/web/package.json b/web/package.json deleted file mode 100644 index d2893c5b1..000000000 --- a/web/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@financial-times/x-docs", - "private": true, - "version": "0.0.0", - "description": "", - "scripts": { - "prebuild": "[ -d '../dist/storybook' ] && cp -r ../dist/storybook static/storybook", - "build": "npm run prebuild && gatsby build --prefix-paths --verbose", - "start": "gatsby develop -H local.ft.com", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "license": "ISC", - "devDependencies": { - "@financial-times/x-logo": "file:../packages/x-logo", - "case": "^1.5.5", - "gatsby": "^2.9.8", - "gatsby-remark-autolink-headers": "^2.0.18", - "gatsby-remark-external-links": "0.0.4", - "gatsby-remark-prismjs": "^3.2.10", - "gatsby-source-filesystem": "^2.0.42", - "gatsby-transformer-remark": "^2.4.0", - "gatsby-transformer-yaml": "^2.1.12", - "graphql-type-json": "^0.3.0", - "prismjs": "^1.16.0", - "react": "^16.8.6", - "react-dom": "^16.8.6", - "react-helmet": "^5.2.0" - }, - "browserslist": [ - "> 1%", - "not ie 11" - ], - "x-dash": { - "engine": { - "server": "react", - "browser": "react" - } - } -} diff --git a/web/plugins/gatsby-transformer-npm-package/extend-node-type.js b/web/plugins/gatsby-transformer-npm-package/extend-node-type.js deleted file mode 100644 index 29cacf455..000000000 --- a/web/plugins/gatsby-transformer-npm-package/extend-node-type.js +++ /dev/null @@ -1,13 +0,0 @@ -const { GraphQLJSONObject } = require('graphql-type-json') - -// This allows us to fetch the entire manifest without specifying every field \0/ -module.exports = ({ type }) => { - if (type.name === 'NpmPackage') { - return { - manifest: { - type: GraphQLJSONObject, - resolve: (node) => node.manifest - } - } - } -} diff --git a/web/plugins/gatsby-transformer-npm-package/gatsby-node.js b/web/plugins/gatsby-transformer-npm-package/gatsby-node.js deleted file mode 100644 index 87ffb1e7b..000000000 --- a/web/plugins/gatsby-transformer-npm-package/gatsby-node.js +++ /dev/null @@ -1,3 +0,0 @@ -// This plugin will create new nodes for any package manifests found by the filesystem plugin -exports.setFieldsOnGraphQLNodeType = require('./extend-node-type') -exports.onCreateNode = require('./on-create-node') diff --git a/web/plugins/gatsby-transformer-npm-package/on-create-node.js b/web/plugins/gatsby-transformer-npm-package/on-create-node.js deleted file mode 100644 index 61b0d0f14..000000000 --- a/web/plugins/gatsby-transformer-npm-package/on-create-node.js +++ /dev/null @@ -1,32 +0,0 @@ -const crypto = require('crypto') - -const hash = (string) => crypto.createHash('md5').update(string).digest('hex') - -module.exports = ({ node, actions }) => { - const { createNode, createParentChildLink } = actions - - if (node.internal.type === 'File' && node.base === 'package.json') { - const json = require(node.absolutePath) - - // Assemble node information - const npmPackageNode = { - id: `${node.id} >>> NpmPackage`, - children: [], - parent: node.id, - internal: { - type: 'NpmPackage' - }, - manifest: json, - name: json.name, - private: Boolean(json.private), - // Mimic remark transformer - fileAbsolutePath: node.absolutePath - } - - // Append unique node hash - npmPackageNode.internal.contentDigest = hash(JSON.stringify(npmPackageNode)) - - createNode(npmPackageNode) - createParentChildLink({ parent: node, child: npmPackageNode }) - } -} diff --git a/web/plugins/gatsby-transformer-npm-package/package.json b/web/plugins/gatsby-transformer-npm-package/package.json deleted file mode 100644 index 25bb6271f..000000000 --- a/web/plugins/gatsby-transformer-npm-package/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "gatsby-transformer-npm-package" -} diff --git a/web/src/components/footer/index.jsx b/web/src/components/footer/index.jsx deleted file mode 100644 index 7a42aa82c..000000000 --- a/web/src/components/footer/index.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' - -const linkProps = { - rel: 'noopener noreferrer', - target: '_blank' -} - -export default () => ( - <footer className="site-footer" role="contentinfo"> - <div className="site-footer__legal-links"> - <a href="http://help.ft.com/help/legal-privacy/cookies/" {...linkProps}> - Cookies - </a> - <a href="http://help.ft.com/help/legal-privacy/copyright/copyright-policy/" {...linkProps}> - Copyright - </a> - <a href="http://help.ft.com/help/legal-privacy/privacy/" {...linkProps}> - Privacy - </a> - <a href="http://help.ft.com/help/legal-privacy/terms-conditions" {...linkProps}> - Terms & Conditions - </a> - </div> - <div className="site-footer__related-links"> - <a href="https://github.com/financial-times/x-dash" {...linkProps}> - x-dash on GitHub - </a> - <a href="https://slack.com/messages/x/" {...linkProps}> - x-dash on Slack - </a> - </div> - <p className="site-footer__small-print"> - <small> - © THE FINANCIAL TIMES LTD 2018. FT and 'Financial Times' are trademarks of The Financial Times Ltd - </small> - </p> - </footer> -) diff --git a/web/src/components/header/index.jsx b/web/src/components/header/index.jsx deleted file mode 100644 index c9e6bac59..000000000 --- a/web/src/components/header/index.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import { Link, withPrefix } from 'gatsby' - -export default ({ showLogo }) => ( - <header className="site-header"> - <div className="site-header__masthead"> - <Link to="/">x-dash</Link> - </div> - {showLogo ? <img className="site-header__logo" src={withPrefix('/logo.png')} alt="" /> : null} - <nav role="navigation" className="site-header__menu"> - <Link to="/docs" activeClassName="is-active"> - Docs - </Link> - <Link to="/components" activeClassName="is-active"> - Components - </Link> - <Link to="/packages" activeClassName="is-active"> - Packages - </Link> - </nav> - </header> -) diff --git a/web/src/components/icon/index.jsx b/web/src/components/icon/index.jsx deleted file mode 100644 index 8fe7f03ca..000000000 --- a/web/src/components/icon/index.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const slate = '#262A33' - -const domain = 'https://www.ft.com/__origami/service/image/v2/images/raw/fticon-v1' - -export default ({ icon, tint = slate, ...props }) => { - const iconUrl = `${domain}:${icon}?tint=${encodeURIComponent(tint)}&source=x-dash` - return <img src={iconUrl} alt={icon} {...props} /> -} diff --git a/web/src/components/layouts/basic.jsx b/web/src/components/layouts/basic.jsx deleted file mode 100644 index ed0ccc0d2..000000000 --- a/web/src/components/layouts/basic.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import Helmet from 'react-helmet' -import Header from '../header' -import Footer from '../footer' - -export default ({ title, children, sidebar }) => ( - <div className="basic-layout"> - <Helmet title={`${title} / x-dash`} /> - <div className="basic-layout__header"> - <Header showLogo={true} /> - </div> - <div className="basic-layout__content">{children}</div> - <div className="basic-layout__sidebar">{sidebar}</div> - <div className="basic-layout__footer"> - <Footer /> - </div> - </div> -) diff --git a/web/src/components/layouts/splash.jsx b/web/src/components/layouts/splash.jsx deleted file mode 100644 index 05a81bc9e..000000000 --- a/web/src/components/layouts/splash.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import Helmet from 'react-helmet' -import Header from '../header' -import Footer from '../footer' - -export default ({ title, children }) => ( - <div className="splash-layout"> - <Helmet title={`${title} / x-dash`} /> - <div className="splash-layout__header"> - <Header /> - </div> - <main className="splash-layout__hero" role="main"> - {children} - </main> - <div className="splash-layout__footer"> - <Footer /> - </div> - </div> -) diff --git a/web/src/components/module-list/index.jsx b/web/src/components/module-list/index.jsx deleted file mode 100644 index 6e38789aa..000000000 --- a/web/src/components/module-list/index.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import { Link } from 'gatsby' - -export default ({ items }) => ( - <ul className="module-list"> - {items.map(({ node }, i) => ( - <li key={`module-list-${i}`} className="module-list__item"> - <Link to={node.path} className="module-list__link"> - <h2 className="module-list__heading">{node.context.packageName}</h2> - <p className="module-list__description">{node.context.packageDescription}</p> - </Link> - </li> - ))} - </ul> -) diff --git a/web/src/components/sidebar/module-menu.jsx b/web/src/components/sidebar/module-menu.jsx deleted file mode 100644 index c53c15656..000000000 --- a/web/src/components/sidebar/module-menu.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { Link } from 'gatsby' - -export default ({ heading, items }) => ( - <div className="site-sidebar"> - <ul className="site-sidebar__list site-sidebar__list--sticky"> - <li className="site-sidebar__item site-sidebar__item--heading">{heading}</li> - {items.map(({ node }, i) => ( - <li key={`module-menu-${i}`} className="site-sidebar__item"> - <Link to={node.path} exact activeClassName="is-active"> - {node.context.title} - </Link> - </li> - ))} - </ul> - </div> -) diff --git a/web/src/components/sidebar/pages-menu.jsx b/web/src/components/sidebar/pages-menu.jsx deleted file mode 100644 index c093d81b9..000000000 --- a/web/src/components/sidebar/pages-menu.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import { Link } from 'gatsby' - -const Group = ({ heading, items }) => ( - <> - <li className="site-sidebar__item site-sidebar__item--heading">{heading}</li> - {items.map((item, i) => ( - <li key={`link-${i}`} className="site-sidebar__item"> - <Link to={item.link} exact activeClassName="is-active"> - {item.title} - </Link> - </li> - ))} - </> -) - -export default ({ data }) => ( - <div className="site-sidebar"> - <ul className="site-sidebar__list"> - {data.map(({ node }, i) => ( - <Group key={`section-${i}`} heading={node.title} items={node.items} /> - ))} - </ul> - </div> -) diff --git a/web/src/components/story-viewer/index.jsx b/web/src/components/story-viewer/index.jsx deleted file mode 100644 index bc647943b..000000000 --- a/web/src/components/story-viewer/index.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import { withPrefix } from 'gatsby' - -const StoryViewer = ({ name }) => { - const queryString = `?path=/story/${name}--*` - const iframeUrl = withPrefix(`/storybook/iframe.html${queryString}`) - const linkUrl = withPrefix(`/storybook/index.html${queryString}`) - - return ( - <div id="component-demos" className="story-viewer"> - <h2 className="story-viewer__heading">Component demos</h2> - <div className="story-viewer__panel"> - <iframe title={`${name} demo`} src={iframeUrl}></iframe> - </div> - <p className="story-viewer__footer"> - <a href={linkUrl}>View in Storybook</a> - </p> - </div> - ) -} - -export default StoryViewer diff --git a/web/src/components/tertiary/links.jsx b/web/src/components/tertiary/links.jsx deleted file mode 100644 index 80cde5d33..000000000 --- a/web/src/components/tertiary/links.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import { Link } from 'gatsby' - -const linkProps = { - rel: 'noopener noreferrer', - target: '_blank' -} - -export default ({ name, manifest, storybook }) => ( - <div className="tertiary-menu"> - <p className="tertiary-menu__heading">Quick links:</p> - <ul className="tertiary-menu__list"> - <li className="tertiary-menu__item"> - <a href={`https://www.npmjs.com/package/${manifest.name}`} {...linkProps}> - NPM - </a> - </li> - <li className="tertiary-menu__item"> - <a href={manifest.homepage} {...linkProps}> - GitHub - </a> - </li> - {storybook ? ( - <li className="tertiary-menu__item"> - <Link to={`/storybook/index.html?path=/story/${name}--*`} {...linkProps}> - Storybook - </Link> - </li> - ) : null} - </ul> - </div> -) diff --git a/web/src/components/tertiary/subheadings.jsx b/web/src/components/tertiary/subheadings.jsx deleted file mode 100644 index 34c05db36..000000000 --- a/web/src/components/tertiary/subheadings.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import GithubSlugger from 'github-slugger' - -const createHash = (name) => { - // This is the same module as used by gatsby-remark-autolink-headers - const slugger = new GithubSlugger() - // This module checks if it is duplicating created URLs, like this: - // slugger.slug('url one') // url-one - // slugger.slug('url one') // url-one-2 - // Therefore we need to create a new class instance every time the function is applied - return '#' + slugger.slug(name) -} - -const scrollOnClick = (e) => { - e.preventDefault() - - const target = document.querySelector(e.currentTarget.hash) - - target && - target.scrollIntoView({ - behavior: 'smooth' - }) -} - -export default ({ items, demos = false, minDepth = 2, maxDepth = 3 }) => { - const headings = items.filter((item) => item.depth >= minDepth && item.depth <= maxDepth) - - if (headings.length === 0) { - // You must explicitly return null for empty nodes - return null - } - - return ( - <div className="tertiary-menu"> - <p className="tertiary-menu__heading">On this page:</p> - <ul className="tertiary-menu__list"> - {headings.map((item, i) => ( - <li - key={`headings-${i}`} - className="tertiary-menu__item" - style={{ paddingLeft: item.depth - minDepth + 'em' }}> - <a href={createHash(item.value)} onClick={scrollOnClick}> - {item.value} - </a> - </li> - ))} - {demos ? ( - <li className="tertiary-menu__item"> - <a href="#component-demos" onClick={scrollOnClick}> - Component demos - </a> - </li> - ) : null} - </ul> - </div> - ) -} diff --git a/web/src/data/docs-menu.yml b/web/src/data/docs-menu.yml deleted file mode 100644 index 84ea35915..000000000 --- a/web/src/data/docs-menu.yml +++ /dev/null @@ -1,39 +0,0 @@ -- title: Get started - items: - - title: What is x-dash? - link: /docs/get-started/what-is-x-dash - - title: Installation - link: /docs/get-started/installation - - title: Working with x-dash - link: /docs/get-started/working-with-x-dash - # TODO - # - title: Thinking in x-dash - # link: /docs/get-started/thinking-in-x-dash -- title: Components in-depth - items: - - title: Overview - link: /docs/components/overview - - title: Creating components - link: /docs/components/creation - - title: JavaScript - link: /docs/components/javascript - - title: Styling - link: /docs/components/styling - - title: Stories - link: /docs/components/stories - - title: Interactions - link: /docs/components/interactions - - title: Testing - link: /docs/components/testing -- title: Integrating x-dash - items: - - title: Using components - link: /docs/integration/using-components - - title: Local development - link: /docs/integration/local-development -- title: Guides - items: - - title: Migrating to x-teaser - link: /docs/guides/migrating-to-x-teaser -# - title: Recipes?? - # This section could be for answering common questions and problems diff --git a/web/src/html.jsx b/web/src/html.jsx deleted file mode 100644 index 910659079..000000000 --- a/web/src/html.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' -import { withPrefix } from 'gatsby' - -export default class HTML extends React.Component { - render() { - return ( - <html {...this.props.htmlAttributes} lang="en"> - <head> - {this.props.headComponents} - <meta charSet="utf-8" /> - <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> - <script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script> - <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:400,700" /> - <link rel="stylesheet" href={withPrefix('/main.css')} /> - <link rel="stylesheet" href={withPrefix('/prism.css')} /> - <link rel="icon" href="/favicon.ico" /> - </head> - <body {...this.props.bodyAttributes}> - <div id="___gatsby" dangerouslySetInnerHTML={{ __html: this.props.body }} /> - {this.props.postBodyComponents} - </body> - </html> - ) - } -} diff --git a/web/src/lib/create-documentation-pages.js b/web/src/lib/create-documentation-pages.js deleted file mode 100644 index 523364419..000000000 --- a/web/src/lib/create-documentation-pages.js +++ /dev/null @@ -1,57 +0,0 @@ -const path = require('path') -const Case = require('case') - -const findPageTitle = (node) => { - if (node.frontmatter.title) { - return node.frontmatter.title - } - - if (node.headings.some((heading) => heading.depth === 1)) { - return node.headings.find((heading) => heading.depth === 1).value - } - - // HACK: use the file name as a last resort - return Case.title(path.basename(node.fields.slug)) -} - -module.exports = async (actions, graphql) => { - const result = await graphql(` - query { - allMarkdownRemark(filter: { fields: { source: { eq: "docs" } } }) { - edges { - node { - id - fields { - slug - source - } - headings { - value - depth - } - frontmatter { - title - } - } - } - } - } - `) - - if (result.errors) { - throw result.errors - } - - result.data.allMarkdownRemark.edges.map(({ node }) => { - actions.createPage({ - component: path.resolve('src/templates/documentation-page.jsx'), - path: node.fields.slug, - // the context object is passed to the template pageQuery - context: { - type: 'documentation-page', - source: node.fields.source, - title: findPageTitle(node) - } - }) - }) -} diff --git a/web/src/lib/create-npm-package-pages.js b/web/src/lib/create-npm-package-pages.js deleted file mode 100644 index 9c97fde17..000000000 --- a/web/src/lib/create-npm-package-pages.js +++ /dev/null @@ -1,60 +0,0 @@ -const fs = require('fs') -const path = require('path') - -module.exports = async (actions, graphql) => { - const result = await graphql(` - query { - npmPackages: allNpmPackage(filter: { private: { eq: false } }) { - edges { - node { - name - manifest - fileAbsolutePath - fields { - slug - source - } - } - } - } - } - `) - - if (result.errors) { - throw result.errors - } - - function hasStorybookConfig(relPath) { - const locations = ['stories/index.js', 'storybook/index.js', 'storybook/index.jsx'] - - return locations.some((location) => { - const filePath = path.join(relPath, location) - return fs.existsSync(filePath) - }) - } - - result.data.npmPackages.edges.map(({ node }) => { - // Package manifest slug will be /package so remove it - const pagePath = path.dirname(node.fields.slug) - const relPath = path.dirname(node.fileAbsolutePath) - - actions.createPage({ - component: path.resolve('src/templates/npm-package.jsx'), - // Remove the file name from the slug - path: pagePath, - // Data passed to context is available in page queries as GraphQL variables. - context: { - type: `npm-package-${node.fields.source}`, - title: node.name.replace('@financial-times/', ''), - source: node.fields.source, - packageName: node.manifest.name, - packageDescription: node.manifest.description, - // Flag if Storybook demos are available for this package - storybook: hasStorybookConfig(relPath), - // Associate readme and story nodes via slug - packagePath: path.join(pagePath, 'package'), - readmePath: path.join(pagePath, 'readme') - } - }) - }) -} diff --git a/web/src/lib/decorate-nodes.js b/web/src/lib/decorate-nodes.js deleted file mode 100644 index 83c71c4cb..000000000 --- a/web/src/lib/decorate-nodes.js +++ /dev/null @@ -1,33 +0,0 @@ -const path = require('path') - -const nodeTypesToSlug = new Set(['MarkdownRemark', 'NpmPackage']) - -const repoRoot = path.resolve('../') - -const createSlug = (file) => { - const pathFromRoot = path.relative(repoRoot, file.absolutePath) - const { dir, name } = path.parse(pathFromRoot) - - // If the file is an index file then use the parent directory name - return path.join(dir, name === 'index' ? '' : name).toLowerCase() -} - -module.exports = (node, actions, getNode) => { - if (nodeTypesToSlug.has(node.internal.type)) { - const file = getNode(node.parent) - - // Group files by source type (currently: docs, components, packages) - // "Source" meaning the name of the filesystem plugin instance - actions.createNodeField({ - node, - name: 'source', - value: file.sourceInstanceName - }) - - actions.createNodeField({ - node, - name: 'slug', - value: '/' + createSlug(file) - }) - } -} diff --git a/web/src/pages/components.jsx b/web/src/pages/components.jsx deleted file mode 100644 index 7218467ca..000000000 --- a/web/src/pages/components.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import { graphql } from 'gatsby' -import Layout from '../components/layouts/basic' -import Sidebar from '../components/sidebar/module-menu' -import ModuleList from '../components/module-list' - -export const query = graphql` - query { - modules: allSitePage(filter: { context: { type: { eq: "npm-package-components" } } }) { - edges { - node { - path - context { - title - packageName - packageDescription - } - } - } - } - } -` - -export default ({ data }) => ( - <Layout title="Components" sidebar={<Sidebar heading="Components" items={data.modules.edges} />}> - <main className="content-container" role="main"> - <h1>Components</h1> - <ModuleList items={data.modules.edges} /> - </main> - </Layout> -) diff --git a/web/src/pages/index.jsx b/web/src/pages/index.jsx deleted file mode 100644 index e1e3a47ee..000000000 --- a/web/src/pages/index.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react' -import { Link } from 'gatsby' -import Icon from '../components/icon' -import Layout from '../components/layouts/splash' -import XLogo from '@financial-times/x-logo' - -export default () => ( - <Layout title="Welcome"> - <div className="hero"> - <div className="content-container"> - <div className="hero__container"> - <div className="hero__logo"> - <XLogo /> - </div> - <div className="hero__content"> - <h1 className="hero__heading">x-dash</h1> - <p className="hero__description">Shared front-end for FT.com and The App.</p> - <Link to="/docs" className="button button--inverse"> - Get started - </Link> - </div> - </div> - </div> - </div> - <div className="content-container"> - <div className="intro"> - <div className="intro__section"> - <h2 className="intro__heading">For FT.com developers</h2> - <ul className="intro__list"> - <li className="intro__item"> - <Icon className="intro__icon" icon="newspaper" /> - No more copy-and-pasting templates. Import components with well-defined, explorable use-cases. - </li> - <li className="intro__item"> - <Icon className="intro__icon" icon="link" /> - Works with the renderer and build tooling you're already using, no need for glue code. - </li> - <li className="intro__item"> - <Icon className="intro__icon" icon="list" /> - Components are logic-less, with denormalised data stored in Elasticsearch, so apps are faster - and simpler. - </li> - </ul> - </div> - <div className="intro__section"> - <h2 className="intro__heading">For component authors</h2> - <ul className="intro__list"> - <li className="intro__item"> - <Icon className="intro__icon" icon="video" /> - Live-editable preview of every component without the headache of setting up a development - server. - </li> - <li className="intro__item"> - <Icon className="intro__icon" icon="users" /> - Write a component once, and it works across every app already using x-dash. - </li> - <li className="intro__item"> - <Icon className="intro__icon" icon="download" /> - Get set up for development quickly. Components and build tools live in a unified monorepo. - </li> - </ul> - </div> - </div> - </div> - </Layout> -) diff --git a/web/src/pages/packages.jsx b/web/src/pages/packages.jsx deleted file mode 100644 index 9b105c955..000000000 --- a/web/src/pages/packages.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import { graphql } from 'gatsby' -import Layout from '../components/layouts/basic' -import Sidebar from '../components/sidebar/module-menu' -import ModuleList from '../components/module-list' - -export const query = graphql` - query { - modules: allSitePage(filter: { context: { type: { eq: "npm-package-packages" } } }) { - edges { - node { - path - context { - title - packageName - packageDescription - } - } - } - } - } -` - -export default ({ data }) => ( - <Layout title="Packages" sidebar={<Sidebar heading="Packages" items={data.modules.edges} />}> - <main className="content-container" role="main"> - <h1>Packages</h1> - <ModuleList items={data.modules.edges} /> - </main> - </Layout> -) diff --git a/web/src/templates/documentation-page.jsx b/web/src/templates/documentation-page.jsx deleted file mode 100644 index 3d5632ebe..000000000 --- a/web/src/templates/documentation-page.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react' -import { graphql } from 'gatsby' -import Layout from '../components/layouts/basic' -import Sidebar from '../components/sidebar/pages-menu' -import Subheadings from '../components/tertiary/subheadings' - -export default ({ pageContext, data }) => ( - <Layout title={pageContext.title} sidebar={<Sidebar data={data.menu.edges} />}> - <div className="content-layout"> - <main className="content-layout__main" role="main"> - <div className="content-layout__main-inner"> - <div className="markdown" dangerouslySetInnerHTML={{ __html: data.markdown.html }} /> - </div> - </main> - <div className="content-layout__tertiary"> - <div className="content-layout__tertiary-inner"> - <Subheadings items={data.markdown.headings} /> - </div> - </div> - </div> - </Layout> -) - -export const pageQuery = graphql` - query($path: String!) { - markdown: markdownRemark(fields: { slug: { eq: $path } }) { - html - headings { - value - depth - } - } - menu: allDocsMenuYaml { - edges { - node { - title - items { - title - link - } - } - } - } - } -` diff --git a/web/src/templates/npm-package.jsx b/web/src/templates/npm-package.jsx deleted file mode 100644 index a513ed4fd..000000000 --- a/web/src/templates/npm-package.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react' -import { graphql } from 'gatsby' -import Layout from '../components/layouts/basic' -import Sidebar from '../components/sidebar/module-menu' -import Subheadings from '../components/tertiary/subheadings' -import Links from '../components/tertiary/links' -import StoryViewer from '../components/story-viewer' - -export default ({ pageContext, data, location }) => ( - <Layout - title={pageContext.title} - sidebar={ - <Sidebar heading={pageContext.source} items={data.modules.edges} location={location.pathname} /> - }> - <div className="content-layout"> - <main className="content-layout__main" role="main"> - <div className="content-layout__main-inner"> - <div className="markdown" dangerouslySetInnerHTML={{ __html: data.markdown.html }} /> - {pageContext.storybook ? <StoryViewer name={pageContext.title} /> : null} - </div> - </main> - <div className="content-layout__tertiary"> - <div className="content-layout__tertiary-inner"> - <Links name={pageContext.title} manifest={data.npm.manifest} storybook={pageContext.storybook} /> - <Subheadings items={data.markdown.headings} demos={pageContext.storybook} /> - </div> - </div> - </div> - </Layout> -) - -export const pageQuery = graphql` - query($type: String!, $packagePath: String!, $readmePath: String!) { - npm: npmPackage(fields: { slug: { eq: $packagePath } }) { - manifest - } - markdown: markdownRemark(fields: { slug: { eq: $readmePath } }) { - html - headings { - value - depth - } - } - modules: allSitePage(filter: { context: { type: { eq: $type } } }) { - edges { - node { - path - context { - title - } - } - } - } - } -` diff --git a/web/static/favicon.ico b/web/static/favicon.ico deleted file mode 100644 index 069b77b918562b4e8ae17af3bba26d6759875c18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6518 zcmeHLYfzNu6@J#yPCIs(bjHj$`O)!Lk{@a@W5=IuO6|o-(iWO{YZGm3%%oU?iY)hK zx!>=1<Q^`IxCHjwU9d(&V@s^H(nwo}1xY)Tu{G37SwX<>^m$i41GpfX{EIvD&b#}4 z?>WzNo^#&Y0w%1$6HkEsIy}1ycpAXEb+f<M059>`vwX%O9C7zKJAls@`|QZa#$V=Q z^J4Ff=hv%OZkC$W8;swoe?At^ypW8GZ)V|gWC1qKeK+^}cqul`6yoxoY+SrS8})8$ zG6J{7V{m^7B%%-!n-5Q^^?n~`X#3whc=VJNawl%3+zEYga6Bw5a9b+2?5)D6)dq<y zh9uKZRT(_h)t~veKu&+4->ZDDXMas3{M&M8&+gG1VQ*|v9HJcv%&@_eSZ9)qQb<#y zNoen}RbYcH9UJ<x;i0|!GwrLoytP$i*G|v)uAc-dTjNt~r;jEK7gm}iGutFt=@5GX zMvmoT<bB#+C^E^_oRu;Y{p9eqeczd9d(THL0uvH35)%)}ONMllud~J^y_qH%ppSpi zpJc+Xt>Ggo3hQIyFx*Tn-F0k?r8fm~lFu%bV!bhLu|A9TIk6bF6Wc}mo(4#7l1X~v zG2ET9w7o44fwfkQ98EAut;HlAnUKzGNS6iTpbt0SU1GldVOP?&DWAL<XKfawB?HnL zjgjVq7;)3KE0J+nO)?sZVF{b>BagZjXTj4#+j`=uzB2<J&C2|RV?&P>p7-d_l?b^R z1LgppYqL5Q7h_9K5k^ayvj*ZS{0`>(MD}O(bDH1ZOM&N%*(9GZrdtIoWFiHlUNg4j z(N9Jq28*qb#$1!Mu|}PK=D_KL*z{opJU3Yv!u*OEgA$8xDJ`n4c%~>37i%(cxzmrW zq1OfSn9RUsvG{C#^T&AiZWT6a^$(uE-ezq6Uo$ouc3<{ht&{86zRRZ8Q{RVc=yE>4 ztwYeEW-N2`gFSxI{PdcMvgf|{;E}-gas<Dcjfp+Ea9NxPSdPt7W5)UsFjEbeRGKEF z9l=YDeW?(^KTL&p2iKMTIS_LRw8GE9VT`uK)})N{g6o!E0PpQQ{~TV)N5~6_@NVTC z`c)eAw>cjVbIwMQLla|IinqUaVqkOKpW@u~#&Ld|A-CgJd1ov?o2P?;{o`23-XiFc zCD6mTW`vQ$kpk{JjnJG;%jYme?15rvI{AC)^Jc_sizNQxZ;BrJhIhss7NU6nv>HJ# zCSqzE$Gyfq&fEe?s)2qm4-%dWImkTZbL}gLSsn+4eZCypdFp+O_A`t_#Xp(E--*zt zLcg!eoBKpU_f1X29|?N77Hfmk;M!jUInc~~x(Slrcz4e}ME)uVMbx9R98$wv_AikO z1N$7Y&ye#i+KOsLs$V5?=s8{A{d1AMyJY$|ue-)A5o?tOg+mB1cR>qpha?g|^BqVG zYbKl;n3ew3Q2VwVJxUHW)PXT7ySX1#Yky*`oaOl66Z-=;?<}^S`lRT;{(;Dj?IN$e zBGY@eYhwp?&+}2pxS~3tWsyS$@vYQD;aj+0GHxHf;!s}?X=s?wLDj|w%=cO1z0aCa zjU4pxyszjIxnATKAtF0pCiZrjd3~2QW;rkq+la$S4n^dUOAcww!C~r{&ibuk+`ib^ zS5-mED<H?ppj{cj>VXEVw&ju!<Lx8vX|_J*oI2>&<DSxI_WVfn-Nb)&=aSfSITVva z-ZAE&5_$o#YKdFU{AaTkd5#fl0{4B^s=E@7?ov3~h}X(BL*@J&HT#J99_0J0sgMbt z8N~7pnT*&>%`JZ8Hn5Fe>7Y2|a4zI=KBlIzF1ap5#j+l_&lk}z_fzR&Zq!+-gP2{6 zcc2jZ--$cOXCJY@LOUU^i7aSp9W?wer&o1eUIi_VxY5Mr+3Q|wVw`s7`Z#fqlZS)) zxT(_#jycac_cvlI{A+xFJA>E{v3AVmR#=FDgal~u)LY?4v!zh~<FnYEjMG_5Y}Tur zc&;d(>&W#i^D)9Y`M8p6?;~OxJnnJeSx@O-GOK+z>%q-4q_1(#_E38_@m<W%DUSV1 z(Zg$QU@q)SO7L~+&>cq|TJxbhmEN2mbMf7*O@)7w`RYqX_bJ*e^z*2{E%X%^hwd!m zJGnOZFy8y{c@CY6KWO`6@Rk0#R%oTfE|2CpmiqJlAr9tO#owcJP>pr&=6j7fawoGE zGocNA&fGhR-H;5ehWV^uUd!0{UFY3NY@Ayi)Vzyzpzaf{B<NkN&rb4GP4PLw+J96I z{anx6@}bq!PX+y!Gp=&t`!wLQcIw?m4lSGyZE?`MxwjeEM&C;vE>XLOoquW{>);;R z%5_ZLJL)-~>bS1fFz#w%Hzq@GCZ878TXO{WgSVLvYUE6UyvyFeSN#yVe@qU`)uD+w zYvA0maqX{D*mE28ZeX*eFbBkLkA|Zo1`ZoJ*r`nqxiJsou-?_ePW<y6_ixt4WE6E^ zK9`yUY9GtbVZOPBHO|GI{jT^lQ*#HkaHK&~d(Ra%JAHRi4~4I4Pc`1r#7-^#90mPW z7}w!LJd2nY=b+a70`6nk^w0Z}55Hjnhen0V=3-r_92v3nGcE_$$x~_$&f=@y!S;`6 z=+o3^`Y<{0{58#U)IuHldH$%jLo4Rqo}CU|-4_+M(Tp1C;`1JIoQrL6Q2FiSJGK8B z9D0d6#9XL-V2qlM(dQ)R?scxqh7X$!YfJ0k%CCT=(l&>=C9ZDdU2#yl+nM*C1#uhL zMz4JKam;Dz@H=9kd}}`br-}#t2sNHgfNMMxYwLLa%uK_S+S8L+tNf2Zs`y^jnc|?Z z+iB<I`NrUE;Q8y-m_ITX{o%w9g?Ly8*SKGO%J^@xKBmpYCpU@4)I=J5ccQW)gk+?^ zn?nw{#OIy)Ufs7*FYfJ{S`Yl;`Fl2GLBNMcpq=5oRQNZj>m>b(s(~YHG4Rf$!FT>P z>>ElDT$%)LKKGAY>QJrL7tX;>&c)@#ZTK;IRS$pV8vGxw8M=y@^}iUlVZ;9@LQ2TN zmjl|_d7ga~Y{OqaIe2*X5uPE1b3u>4KW<~J(YN#d*;;~$wi37=Msve{VXw~Rw~xZ5 qAH{^U`oH@vv>E6{(4})<;QhNBYsR|~v=Ha<@5c%}R^ZF6!2bb-MmWm= diff --git a/web/static/logo.png b/web/static/logo.png deleted file mode 100644 index 9d88c0d1585e8763f6ca40095c4f3f839af216c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4378 zcmV+#5#{cQP)<h;3K|Lk000e1NJLTq002Ay002A)1^@s6I{evk000o-Nkl<Zcmd7Y z1#}!qwxHqvMnrN|32e*F{g|1VtjAnu)*hZ&W@ct)#z&=X&&spR(EXN~nR|Ai*)B<z z3NkYy?&{>R^H`SJsD@7`%=G(4LRHFf7r)_izkNLo@$-Ry$lKUp_^xmCF8*g=${o1h z_tk%f*V9h_>af-2mN@s#wQt`G{6*e|)`#yOHyWOajsCdi=AFR)>jt+*IMLO=GHl)Q zvJ?AVZ49gz)#TGx&+&GPey#cTu?mydme+GD$#?lhdzqPe_v=MFu5!#Z{>pIV>dT4P z?^y=VFwOalwewYK-K6&6Z4>>8#g~n9Clf&`gHXqs)MVeI11_)SYbQpVvuDq8bp4g# zV|jhqiRoiMbNxKl7*w^&teF5~-eS)meAd{Rd<&$C8p*5!poukkl7CO5;W98hKH(#N zXc(96<&m@BbG>c**wi{ym4IT!RMsSugVw!elYVLO^f)(zi8VzTG$24~5`-m+(!`cg zGWebYKfnCKbpObt&Huc5mBR&Bf7fua_u6H`@_XvgawaQ|#NHG%5>=|?t(bHd`zhl@ zk}YeiDyjmYfHMXs1^_{5LQQ6}@5u^|Oxgi_CO<r!C*g7;@;&o*1_+O>49KkzRJom3 z1++>WTkV^5On0%LK6YZZtjS9x0hF5zrW;P%21EpasGd<;ea|)By&RWGH);1o`J?CN zjsLMk%y-r?Vlb@aIflqVJtuPs)}$!HO^S4rwwN+?Zqlf};n6Q0d`cH9NNG&TNivf# zlNv!#Ku}U6-HEIzmfIrP2b>A*lOSL3qvFJ{R+MDc0<oL!ysb%$Cv`bSEDER!z|H6? zMJ7$~Z&>tRb9jOhwjh>b3Z$QI4@F6g#bQ9D-|AKj&N!5nugAHk+$+T|`gdpdfA<fR zF1>OXyDlOqil}l^8w0+Xk=PVxbCeDOL?TXXRVL{~h0~9AUo{S9F}XI|D-prj3Q$gB z#B|Xel&Fy;S>;^*$n@3a!A0QTI%kILuKcFw9?{=>$%@$)Sof-Q(ex*Uj%rIPjx-$t zf`}jhA~=!Cikl3|Gp82)iGwG`7MTE5&_M215$x**3qocST0ns!e}p1kodTCe>B@5e z?$bt(1K|06&$9Q6E~R3xI5BI!O9m%G;T(mvq}Cz`Y6445xm6n@MP61_QciZ-hu_{! z_>}RPTqb+<tn%uyCdHcaq^*49+o`dr<P6Hkajw|$zP$uI%MH#4&rN>tyPoCXmt9Q7 zZYQGaqb#Mc4l6=LQ0P{I$C40^oX8AWQlP{t6}xrtP-pL_#|dT&Lz%k-RI#S&_nX}K zFk-r8jl+t?io*kSvWA#+>ix2h?Smn3tL5OgKWp}W)kWvxmD0+(5kuf8C6XJ@EG7W> zsN_jO>Qa0RIAhDyxUCTHf70P_vd)^V2xSO?#3qcgHH>69<GMH}7Sk`&#RrsmXc2L! z$h4-uh~qhKBY);Q{uL)FuKlvjr&pfbE|O}!jYpHMBqSzfp^ing1ci;kIf}GERZ(CO z98qN+qJQ5#Mt9pd5x1q#-9dE}f=`H=;z>|7{YDmU{9Vy^%XYD5+Tbq2vDl9M*>61- z-1`*!CofpchYhmkH6jQAMl6ZRX|e2kOJXz5kIr+G0P`StwFn$Whr32w``P2R4O`I1 zcA~e$Xl}Xwrvw}Ttw!dt#jE1P0ZhHblVW@Fp>O?1+{qK>M>kWk{n;B7gP};*Kpcu< z?T9QJp{jjw8--aAtQbVh%!l^<uNVFFnXejyoxHfSQ_Y%Xf2X9;5WMAfk%Wb2T=z@5 zOOJ7Y9L6@sV~+>_ICt~3eddCNE5xwgh9$;koEtuY69*_kbAn`GQ=q@-bI=+hbkPwr zpSN;s(x1NZCF9h%?hfiOmKSz*h^nXxU91WKs!CLdA(Cfj$n&!-e9a;>%=}`yjElR) zb{shF=A+>Q&pn}k<Wi!t?TlLw;_#uxIoF@LMLmRwnheBX0B4js3PB@YU0G(?<lM>{ zH=|QK9B1k#3S}ETS(30Z8u9Yp9x)g+N*5m^sDh$6laXiZ2(Xe4Sb^z*ziVty{@D-w zU+&r7ufAZ?>cb+kR-{9QH@30Y0x-}K)FfaMyb3cf1Qnv>JiY!Xd6Le(_wRy$wSx~y z9F;x>ZW4r7cIUjZGY5pDlwtW8NixJ42k125?=`mLa_6{bXMX2%v;E(AF+r}NaSa6! zy46!8HbBIg6oVm}j3-t%NSs5JQjKhb1vR;k5dc*P-gIk}jyT8y2g@3feoK?3!#-At zJCSfdu^st$@AxS9Yqt9vH>)haa<<%9YgGx|goWdT+6);EpQ1=JROy&5LW!Y7V?hWI zTHC)U5x~d5$EQ;o9}xi&!)Wjnxhv7*lYK*jJ7NEKY;KIsZHpx9%aC$ARVB$%iorP+ z%K`+9Sj3p_Yhw(e8~7$AcmWhTawAZf*Gpd5*(GYEDmEAv=TSL~)wYj)vw}M-`lmOo z$*x3-H6O!~*sLn4R?cG7a4=KqhAu=EKyg{g>dNOJJ|)IL43Xd?Aw*(~#Gn9N+rQ4v z!gF@@)4-W&BXxhf_#ot8zx$&+jFozF7@c1cq>ENn%9YXKT1Q*D!Vuv4fgJe{sn#E( zC^m3Sg^1pAIa5&RqN5T*U}s*k2<bFXKk#JfC;XmI@UZgb)@Clrwg~I#VQjIoLR{+2 zaUuc-Gr2Kk#kh>o#wR0UNtz*|hLb6&U11io=|^{V`pG_A@cUojG~~0%=ElaO+xcj? zzMQuZlw0&uR*jS^k5G)xp&GEh#D*NuW3h>;P${PK)}KafN3Q<dT~1B)cQ%vSY-_9Q zJ_dUIl%Wj?qw}APF%AHUj-(wNi*+UksiuvF(~9lL?l0WsG%XJP>Nn;|`j-^eqDF4z zz0#Tu%GF2E{^z){tu1tJp3Mh4O{+h6+7I!ZdgU}_kFJeOtu~^F07eiaAcoX{i50O8 zh)O@rXj*J=fI=q+oVBhQamt)^%4yll|L%Bgurbl0sbUNmj?!o#fF=uQ9MUGBaw9l{ zPDaFG(NQAVhkR`MS6Qb)_J6?+r{P+&HI{O?m1b3$57z+UcEP7ab#w#>f;34No-_kQ zyW^IdoN=HsueEvKX+_J<DFipy{>YeOI9ZHVD<3>rUNIP*1?i(v6}-;@;Rpe|VCO5i z_7VEuqjVDESOz=0x?7*hq}^+~Jd~pRBjX}-6E9RjBO*e*oB>cBMoWM5Zvf$#7!d>R zj6>^&SUYYNx{st9rrBh^nDemF&JF_)Dh+>poQmCwCYCL|K3Y{7teitch|$qg1&xR* z07-rh>vFI)!9>C`<yNh>*wj_*CtLeje5h!*Z!HgE<JDh1cB_v~6iP1<HRfm#+=z}; zqw{nVjyEF}I*Lozajxork1bj-N|>jBKu1~BOBQ)rnk<>Lv3}ra7gFxq6CeGBW1Cf5 z0V#cqAP}8Fj6np9wRFo_#2O+239%0~VgN<lkR)9Jl%9gkJR*vpXP|;AG|tdSz^YVE z(k-ve14X<0p6o||#@O0wOHERG1q8$iRcSG2&md`nK!5Ba5Nktd6a<`c_}K1RH=ryw zR(<pwqhQj2lO=wZ(cp;2lB&`Kk2S75Ir@}?SD*jD80WScotM~kBsCZdhKThwb~+`l zu0T<ss&o{PE|$bPRGAXOI2mmq!e2~mvfhT4p3*aU#1t*fG=~=4jpx-N-i%JRyl?L- zTK?@X-r-(6x_7vZ#Y{|^Rhp#W21Feqs$H;=9A{QRKnDYKGy-El6%7V68-OycWB+=V zta@s*aQQYWT{H%8Kq5#YV4-zGUVPOd`w?kDfn^&}z*$GrnAyi(4)=V?lE3)weeULK zAN>vE#JVjJDZOfcf{Ag6Q41&{35h*JM-|O7baf5V1c$@#Wjjr1FLtrL8_`9vO?DV9 z9x(y}sG=?-*cD!Qxg|!hvo0DRmBuRo8XYfOwR7Lz+Y=7MU3|%zUp0;)PL@qw?l0yn zmJ7NwX1yS0K$3uhYD9^2M3A~hUVD{fu>^G5u)K&3&te0&u6p^AXX7AVlvJi^n$l5F z5lBl=K>-B9zz|b#M!MAzk2P9an@oW5Lmk84@Oi&&EOoWj&YH5V0|@j~Q3;rHtHf8X zfsddO#PrcALfqNesTa#jwx8^Av;X%`pUa>6s!NGYuOv2EYeIvjC00^A%B^B$K$Juw z^lvbDl%%rGZ3Tbl2YBvvV?X=1jU8r-oHDwUbZKbkE$S5nK#0u}Qj2V?p|9?s8W0?y zBxy=?$#hvSF0q_F%Uvx0*K^PE85b_5Cfjy-vF=R{2%y|9mVyyT=Ry+{=U`ELoN;AS zhOOOn|8=9~PyDQpbGvsu`p3FQmOyu%?FeX8{M;X|7L5^c3B{A2i6kkytT8)ak`ZoD zqG>t}R_^~jsPc0*)4}=`C)Jvdo~BV=c%?m_Ent>P*pFP@gXx~7NgL)>3nFuqoqSBo zkNn~nxz%@_`2*uzl8Gu+AA2jIn{qpE2+KhJ_@`sCf*4zLsT9={;Ie6G!~Mqg+}Us5 zOcHm+7_&B8G>5_c#y%chE4wd6KDry3uPkttp%|tlnVI?MkDa~c!@u(?{Wj*;jWZoi zh^~)4iHM*mZQUZpc}z9{?@7FXO{bEdd*ImK+3(z}vShn%Wc{Nr&QA_j5Ce-Q^8B*F zlpsb>Rg5@9nAM>^5-or3cfJC^H}hM@sWB62%gUcTSQH2vY1Pw4PXGqh(-@PHw4hO^ zv7hY2%RlR<Z>o3OFTNU%-<$}Lfs9~jl~?vaBcO^ZU9^Y=WENu6N6TON{X2Zs!7m*r zd9g)I%GhRihzxiQC;*DH#bNL#k*Giur}NN%0?DcIXMXDDvQD-Y*1c~*fE*YA`HcxL zm6vxxgk!9A0CQFCj%oRyzxvz1Vs-G;KTeV&4Rwmq6h)6j1`R}RzbZxY>@1_|F?88Z z>(Ed3)LqMe@&#*>D-^8zqJd2MX^6mVp}czi<X{mHaa`MPnpgMdU+?Bg@xO|KzmOsC zYUe_&HLV_x6mBCzkquF=({LE;`q<NVe)&Vsz4+({E~Ud_+ez9@8l@MN6N$j04(u-A z`fTwRBwPM3`Tf7)BYe+S{Jt!IKK@Xao_){$wSD5U1yvB?WJ>BXhSeucV=Mh+-)OHq z^}!3)C0BrTAKE@@Z>CHSK%@^PMrC@?a?m*HcKM9GzVVZRTK>?_RRF&8JAYnQ?mYTK zxwG$??alG~3*xLseE>KT?~LGVH7$y#``9<ji;ui>Gfj(agl^IzuzR5F%s_;0&FjEx z*Bb&E!7rbY^IdEWAb;o&DK~xRr~ToqymtME@+^JNqHep`Fsljsi*D)~mBd*T-BI`n zUF=(6`5C|Mg0b$yVq`6Zmg&B7eF3W7R1f1@oGb9R#U2Sh7M&*VSu`ympxOn?ydfMc zr_Hi|*7KG*_>^D2S>(mGP2Bp+I}I=I1+E=5EZazy7PReQ?Damj*Kg@wx4rx5ZqmSw z$;#Bv`$v{<xoiCF3zDW+UYN)A|MSwE5QNl~wC(Z9zV3vNO*%>5(?%s4Gi~d-pX}Sk z{|-NGvvv9QtghGF1`ZC|XBMA$?0D?)2_N@!4=4T6-TFsuo_|LZ<Q?5)--iBM{oV@} z`{(y(2Y-9`<iQ8~*b~U#{G0!qJ9*sx^J=iS_bI@C=56dh&))O-g<X3dm~jXHAFUU{ UR+uo39smFU07*qoM6N<$f<ZZh8UO$Q diff --git a/web/static/main.css b/web/static/main.css deleted file mode 100644 index 485faa001..000000000 --- a/web/static/main.css +++ /dev/null @@ -1,714 +0,0 @@ -:root { - /* primary colors */ - --o-colors-paper: #fff1e5; - --o-colors-black: #000000; - --o-colors-white: #ffffff; - --o-colors-claret: #990f3d; - --o-colors-oxford: #0f5499; - --o-colors-teal: #0d7680; - - /* secondary colors */ - --o-colors-wheat: #f2dfce; - --o-colors-sky: #cce6ff; - --o-colors-slate: #262a33; - --o-colors-velvet: #593380; - --o-colors-mandarin: #ff8833; - --o-colors-lemon: #ffec1a; - - /* tertiary colors */ - --o-colors-candy: #ff7faa; - --o-colors-wasabi: #96cc28; - --o-colors-jade: #00b359; - --o-colors-crimson: #cc0000; - - /* color shades */ - --o-colors-claret-30: #4d081f; - --o-colors-claret-40: #660a29; - --o-colors-claret-50: #800d33; - --o-colors-claret-60: #990f3d; - --o-colors-claret-70: #b31247; - --o-colors-claret-80: #cc1452; - --o-colors-claret-90: #e6175c; - --o-colors-claret-100: #ff1a66; - --o-colors-oxford-30: #082a4d; - --o-colors-oxford-40: #0a3866; - --o-colors-oxford-50: #0d4680; - --o-colors-oxford-60: #0f5499; - --o-colors-oxford-70: #1262b3; - --o-colors-oxford-80: #1470cc; - --o-colors-oxford-90: #177ee6; - --o-colors-oxford-100: #1a8cff; - --o-colors-teal-20: #052f33; - --o-colors-teal-30: #08474d; - --o-colors-teal-40: #0a5e66; - --o-colors-teal-50: #0d7680; - --o-colors-teal-60: #0f8e99; - --o-colors-teal-70: #12a5b3; - --o-colors-teal-80: #14bdcc; - --o-colors-teal-90: #17d4e6; - --o-colors-teal-100: #1aecff; - - /* color washes (not really from o-colors) */ - --o-colors-black-wash: #ebf2f0; - --o-colors-claret-wash: #fbf5f7; - --o-colors-oxford-wash: #f9fbfd; - --o-colors-teal-wash: #f3f8f9; - - /* font-stacks */ - --font-sans: Lato, -apple-system, BlinkMacSystemFont, sans-serif; - --font-mono: "Space Mono", Menlo, Monaco, Consolas, monospace; - - /* breakpoints (can't be specified with custom properties so only for reference */ - --break-small: 50em; - --break-large: 70em; - - /* containers */ - --container-small: 360px; - --container-readable: 760px; - --container-large: 960px; -} - - -/* Globals */ -body { - margin: 0; - color: #24292e; - font-family: var(--font-sans); - font-size: 16px; - line-height: 1.5; - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - /* name specific properties to avoid animating the focus outline */ - transition: color 0.1s, background-color 0.1s, border-color 0.1s, opacity 0.1s; - color: var(--o-colors-teal); - text-decoration: none; -} - -a:hover, -a:focus { - color: var(--o-colors-teal-80); -} - -a:focus { - outline: 2px solid var(--o-colors-teal-100); - outline-offset: 2px; -} - -img { - border: 0; - max-width: 100%; -} - - -/* Preformatted text and code */ -pre, -code { - tab-size: 2; - hyphens: none; - font-size: 0.875rem; - font-family: var(--font-mono); -} - -pre { - background: var(--o-colors-teal-wash); - padding: 0.5rem 0.75rem; - overflow: auto; -} - -pre code { - display: block; - white-space: pre; -} - -:not(pre) > code { - padding: 0 0.25em; - color: var(--o-colors-claret-60); - background: var(--o-colors-claret-wash); -} - -table { - outline: 3px solid var(--o-colors-black-wash); - border: 1px solid #ddd; - border-collapse: collapse; -} - -thead th { - background: var(--o-colors-black-wash); -} - -th { - text-align: left; -} - -th, -td { - padding: 0.25rem 0.5rem; - border: 1px solid #ddd; - border-top: 0; - border-bottom: 0; -} - -th:first-child, -td:first-child { - border-left: 0; -} - -/* Buttons */ -.button { - display: inline-block; - padding: 0.75rem 1.25rem; - border: 2px solid; - font: inherit; - font-weight: bold; - color: var(--o-colors-teal); - background: none; -} - -.button:hover, -.button:focus { - color: var(--o-colors-teal-80); -} - -.button--inverse { - color: var(--o-colors-white); - background: transparent; -} - -.button--inverse:hover, -.button--inverse:focus { - color: var(--o-colors-white); - background: rgba(255, 255, 255, 0.25); -} - -/* Re-usable layout components */ -.content-container { - max-width: var(--container-small); - margin-left: auto; - margin-right: auto; - padding-left: 1rem; - padding-right: 1rem; -} - -@media screen and (min-width: 50em) { - .content-container { - max-width: var(--container-large); - } -} - - -/* Splash layout */ -.splash-layout { - display: grid; - /* 100% height layout all the time */ - min-height: 100vh; - grid-template-rows: auto 1fr auto; -} - -/* Basic layout */ -.basic-layout { - display: grid; - min-height: 100vh; - grid-template-rows: auto 1fr auto; - /* "auto" = max content, so nothing will wrap without specifying a width */ - grid-template-columns: 100%; -} - -.basic-layout__header {} - -.basic-layout__content {} - -.basic-layout__sidebar { - margin-top: 1rem; -} - -.basic-layout__footer {} - -@media screen and (min-width: 50em) { - .basic-layout { - grid-template-columns: 1fr 4fr; - grid-template-areas: "header header" "sidebar main" "footer footer"; - } - - .basic-layout__header { - grid-area: header; - } - - .basic-layout__content { - grid-area: main; - } - - .basic-layout__sidebar { - grid-area: sidebar; - margin-top: 0; - } - - .basic-layout__footer { - grid-area: footer; - } -} - -@media screen and (min-width: 70em) { - .basic-layout { - grid-template-columns: 1fr 5fr; - } -} - -/* Header */ -.site-header { - position: relative; - color: var(--o-colors-white); - background: var(--o-colors-slate); - text-align: center; -} - -.site-header__masthead { - padding: 0.5rem 0.75rem; - font-size: 1.25rem; - font-weight: bold; - text-transform: uppercase; -} - -.site-header__masthead a { - color: inherit; -} - -.site-header__logo { - display: none; -} - -.site-header__menu { - display: flex; -} - -.site-header__menu a { - flex-grow: 1; - padding: 0.5rem 0.75rem; - font-size: 0.875rem; - font-weight: bold; - text-transform: uppercase; - color: inherit; - opacity: 0.65; -} - -.site-header__menu a:hover, -.site-header__menu a:focus, -.site-header__menu a.is-active { - opacity: 1; -} - -.site-header__menu a.is-active { - color: var(--o-colors-teal-100); -} - -@media screen and (min-width: 50em) { - .site-header { - display: flex; - align-items: center; - text-align: initial; - padding: 0.75rem 1.25rem; - } - - .site-header__masthead { - padding: 0; - } - - .site-header__logo { - display: initial; - position: absolute; - width: 30px; - left: 50%; - margin-left: 15px; - } - - .site-header__menu { - margin-left: auto; - } - - .site-header__menu a { - padding: 0; - margin-left: 1rem; - } -} - - -/* Sidebar */ -.site-sidebar { - box-sizing: border-box; - min-height: 100%; - padding: 0.75rem 0.5rem; - background: var(--o-colors-teal-wash); - border-right: 1px solid rgba(0, 0, 0, 0.05); -} - -.site-sidebar__list { - margin: 0; - padding: 0; -} - -.site-sidebar__item { - display: block; - margin-top: 0.25rem; -} - -.site-sidebar__item:first-child { - margin-top: 0; -} - -.site-sidebar__item--heading { - margin: 1.25rem 0 0.5rem; - font-weight: bold; -} - -.site-sidebar__item a { - color: var(--o-colors-black); - opacity: 0.65; -} - -.site-sidebar__item a:hover, -.site-sidebar__item a:focus, -.site-sidebar__item a.is-active { - opacity: 1; -} - -.site-sidebar__item a.is-active { - color: var(--o-colors-teal); -} - -@media screen and (min-width: 50em) { - .site-sidebar { - padding: 1.5rem 1.25rem; - } -} - - -/* Site footer */ -.site-footer { - padding: 1.25rem 1rem 1.5rem; - font-size: 0.75rem; - text-align: center; - color: var(--o-colors-white); - background: var(--o-colors-slate); -} - -.site-footer a { - margin-left: 0.75rem; - color: var(--o-colors-teal-100); -} - -.site-footer a:first-child { - margin-left: 0; -} - -.site-footer__legal-links {} - -.site-footer__related-links {} - -.site-footer__small-print { - margin: 0.5rem 0 0; -} - -.site-footer__small-print small { - font-size: inherit; -} - -@media screen and (min-width: 50em) { - .site-footer { - display: flex; - flex-wrap: wrap; - text-align: left; - font-size: 0.875rem; - padding-left: 1.25rem; - padding-right: 1.25rem; - } - - .site-footer__related-links { - margin-left: auto; - } - - .site-footer__small-print { - width: 100%; - } -} - - -/* Content layout */ -.content-layout {} - -.content-layout__main {} - -.content-layout__main-inner { - margin: 0 auto 2rem; - padding-left: 1rem; - padding-right: 1rem; - max-width: var(--container-readable); -} - -.content-layout__tertiary { - display: none; -} - -.content-layout__tertiary-inner { - margin: 1.5rem 0; - padding: 0.25rem 1rem; - border-left: 2px solid rgba(0, 0, 0, 0.1); -} - -@media screen and (min-width: 70em) { - .content-layout { - display: grid; - grid-template-columns: auto 220px; - } - - .content-layout__tertiary { - display: block; - } - - .content-layout__tertiary-inner { - position: sticky; - top: 0; - } -} - - -/* Tertiary menu */ -.tertiary-menu { - margin-top: 1rem; - font-size: 0.875rem; - background: var(--o-colors-white); -} - -.tertiary-menu:first-child { - margin-top: 0; -} - -.tertiary-menu__heading { - margin: 0; - font-weight: bold; -} - -.tertiary-menu__list { - padding: 0; - margin: 0.25rem 0; -} - -.tertiary-menu__item { - display: block; - margin-top: 0.25rem; -} - -.tertiary-menu__item:first-child { - margin-top: 0; -} - -/* Hero */ -.hero { - padding: 2.5rem 0; - color: var(--o-colors-white); - background: var(--o-colors-slate); -} - -.hero__container {} - -.hero__logo { - /* Chrome requires explicit size to render implicit height */ - width: 100%; - margin: auto; -} - -.hero__content { - max-width: 480px; - margin: auto; - text-align: center; -} - -.hero__heading { - font-size: 2rem; - line-height: 1; - text-transform: uppercase; -} - -.hero__description { - margin: 1.25rem 0; - font-size: 1.25rem; -} - -@media screen and (min-width: 50em) { - .hero { - padding: 3.75rem 0; - } - - .hero__container { - display: grid; - grid-gap: 1.25rem; - grid-template-columns: 1fr 2fr; - } - - .hero__content { - text-align: left; - } - - .hero__heading { - margin: 0; - font-size: 3rem; - } - - .hero__description { - font-size: 1.5rem; - } -} - - -/* Intro */ -.intro { - margin: 1rem 0; -} - -.intro__section {} - -.intro__heading { - margin: 0; - font-size: 1.25rem; -} - -.intro__list { - margin: 1.25rem 0; - padding: 0; -} - -.intro__item { - display: block; - margin-top: 1.5rem; - /* encapsulate floating icon */ - overflow: hidden; -} - -.intro__icon { - float: left; - width: 50px; - margin-right: 0.75rem; -} - -@media screen and (min-width: 50em) { - .intro { - display: grid; - grid-gap: 1.25rem; - grid-template-columns: 1fr 1fr; - margin: 2.5rem 0; - } - - .intro__heading { - font-size: 1.5rem; - } - - .intro__item { - margin-top: 1.5rem; - } -} - - -/* Module list */ -.module-list { - margin: 0; - padding: 0; - list-style: none; -} - -.module-list__item { - margin-bottom: 1.25rem; -} - -.module-list__link { - display: block; - padding: 0.75rem 1rem; - background: var(--o-colors-teal-wash); - border: 1px solid rgba(0, 0, 0, 0.05); -} - -.module-list__link:hover, -.module-list__link:focus { - border-color: var(--o-colors-teal-70); -} - -.module-list__heading { - margin: 0 0 0.5rem; - font-size: 1rem; - font-weight: normal; -} - -.module-list__description { - margin: 0; - color: var(--o-colors-slate); -} - -@media screen and (min-width: 50em) { - .module-list { - display: grid; - grid-gap: 1.25rem; - grid-template-columns: 1fr 1fr; - } - - .module-list__link { - box-sizing: border-box; - height: 100%; - padding: 1rem 1.25rem; - } - - .module-list__heading { - margin: 0; - font-size: 1.25rem; - } -} - -/* Storybook viewer */ -.story-viewer { - margin-top: 2.5rem; - border-top: 2px solid rgba(0, 0, 0, 0.1); -} - -.story-viewer__header { - margin: 1.5rem 0; -} - -.story-viewer__list { - margin: 1.5rem 0; - display: flex; - list-style: none; - padding-left: 0; -} - -.story-viewer__panel { - height: 20rem; - resize: vertical; - overflow: auto; -} - -.story-viewer__panel iframe { - display: block; - width: 100%; - height: 100%; - border: 0; -} - -.story-viewer__footer { - margin: 0.5rem 0 1.5rem; - text-align: right; -} - -.story-viewer__footer a { - font-size: 0.875rem; - color: var(--o-colors-oxford); -} - -.story-viewer__footer a:hover, -.story-viewer__footer a:focus { - color: var(--o-colors-oxford-100); -} - -.story-viewer__footer a:after { - content: ' →'; -} diff --git a/web/static/prism.css b/web/static/prism.css deleted file mode 100644 index 4e8016d94..000000000 --- a/web/static/prism.css +++ /dev/null @@ -1,57 +0,0 @@ -.token.punctuation, -.token.prolog, -.token.doctype, -.token.cdata { - color: var(--o-colors-black); -} - -.token.comment { - color: #999; -} - -.token.operator { - color: var(--o-colors-mandarin); -} - -.token.boolean, -.token.number { - color: var(--o-colors-velvet); -} - -.token.regex, -.token.attr-value, -.token.string, -.token.property, -.token.url { - color: var(--o-colors-oxford-90); -} - -.token.selector, -.token.tag { - color: var(--o-colors-teal); -} - -.token.function, -.token.inserted { - color: var(--o-colors-jade); -} - -.token.attr-name, -.token.rule, -.token.keyword, -.token.important, -.token.deleted { - color: var(--o-colors-claret); -} - -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} From f2764ff8ca30ccf75b5bc47bb2bae381ec5cbcd2 Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Wed, 9 Jun 2021 15:45:14 +0100 Subject: [PATCH 708/760] fix circleci config and added npm scripts for sb --- .circleci/config.yml | 6 +----- package.json | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 88b29563b..817aa5569 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,10 +40,6 @@ references: restore_cache: <<: *cache_keys_root - restore_cache_docs: &restore_cache_docs - restore_cache: - <<: *cache_keys_docs - # # Filters # @@ -108,7 +104,7 @@ jobs: command: make test - run: name: Run storybook - command: npm run storybook:ci + command: npm run start-storybook:ci publish: <<: *container_config_node diff --git a/package.json b/package.json index 2231d7ab0..547afda3f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", + "start-storybook:ci": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com --ci --smoke-test", + "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", "deploy-storybook:ci": "storybook-to-ghpages --ci --source-branch=main", "start-docs": "(cd web && npm start)", "build-docs": "(cd web && npm run build)", From 9e6bf4216a8c71e87664af2e665242859ac881b4 Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Wed, 9 Jun 2021 16:38:43 +0100 Subject: [PATCH 709/760] updated readme --- readme.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 13a861464..b9d288ca0 100644 --- a/readme.md +++ b/readme.md @@ -13,8 +13,104 @@ Check out the [getting started] guide to begin hacking on x-dash. [Google Slides]: https://docs.google.com/presentation/d/1Z8mGsv4JU2TafNPIHw2RcejoNp7AN_v4LfCCGC7qrgw/edit?usp=sharing [getting started]: https://financial-times.github.io/x-dash/docs/get-started/installation -### How is that not Origami? +## How is that not Origami? Origami components are designed to work across the whole of FT and our sub-brands, making as few assumptions as possible about the tech stack that will be consuming them. Origami components don't contain templating, only styles and behaviour. It's up to each individual app to produce markup for components. x-dash aims to complement Origami by providing easily reusable and composable templates, flexibly enough to work across Next and Apps apps. + +## Installing x-dash + +### Requirements + +To get started with x-dash, you'll need to make sure you have the following software tools installed. + +1. [Git](https://git-scm.com/) +2. [Make](https://www.gnu.org/software/make/) +3. [Node.js](https://nodejs.org/en/) (version 12) +4. [npm](http://npmjs.com/) + +Please note that x-dash has only been tested in Mac and Linux environments. If you are on a Mac you may find it easiest to install the [Command Line Tools](https://developer.apple.com/download/more/) package which includes Git and Make. + +#### Recommended + +To aid the development of interactive components with Storybook it is recommended to install the React Developer Tools for your browser. These addons aid debugging by displaying the rendered component tree and provide access to view and edit their properties: + +- [React Developer Tools for Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) +- [React Developer Tools for Firefox](https://addons.mozilla.org/en-GB/firefox/addon/react-devtools/) + + +### Project setup + +1. Clone the x-dash Git repository and change to the new directory that has been created: + + ```bash + git clone git@github.com:financial-times/x-dash + cd x-dash + ``` + +2. Install all of the project dependencies (this may take a few minutes if you are running this for the first time): + + ```bash + make install + ``` + +3. Build the current set of x-dash components and start Storybook to view: + + ```bash + make build + npm run start-storybook + ``` + +## Working with x-dash + +The project repository is a monorepo which means all of the tools, packages and components are kept in one place and can be worked upon concurrently. + + +### Repository structure + +The repository groups related code together in directories. UI components are stored in the `components` directory, documentation files in the `docs` directory, additional public packages in the `packages` directory and tools to aid working with x-dash are in the `tools` directory. + +``` +├ components/ +│ └ x-component/ +│ ├ readme.md +│ └ package.json +├ packages/ +│ └ x-package/ +│ ├ readme.md +│ └ package.json +├ tools/ +│ └ x-docs/ +│ └ package.json +├ readme.md +└ package.json +``` + +### Using Storybook + +[Storybook] is a development environment and showcase for UI components. It makes working on and sharing UI components easier by providing a richly configurable environment. + +After installing x-dash you can start Storybook by running the following command from the repository root: + +```sh +npm run start-storybook +``` + +This command will start a server running on [local.ft.com:9001] and generate an application presenting all of the components configured to use it. Changes to these components can be updated in real-time speeding up the development process. + +Data properties passed to these component may also be configured in-situ and these changes will be reflected in the URL making it possible to share specific configurations. + +[Storybook]: https://storybook.js.org/ +[local.ft.com:9001]: http://local.ft.com:9001/ + + +## Coding standards + +The best way to ensure you stick to the x-dash code style is to make your work consistent with the code around it. We also provide a [Prettier] configuration to automatically format files and run [ESLint] before any tests. See the [contribution guide] for more information. + +[Prettier]: https://prettier.io/ +[ESLint]: https://eslint.org/ +[contribution guide]: https://github.com/Financial-Times/x-dash/blob/HEAD/contribution.md + +For more in-depth information visit the [Wiki](https://github.com/Financial-Times/x-dash/wiki) \ No newline at end of file From 457a1cc29280c59b97c14e04174e07b497e6655b Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Thu, 10 Jun 2021 14:38:28 +0100 Subject: [PATCH 710/760] remove unused docs and sb build scripts --- package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index 547afda3f..17d5013c7 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "scripts": { "clean": "git clean -fxdi", - "build": "athloi run build --concurrency 3 && npm run build-storybook", + "build": "athloi run build --concurrency 3", "build-only": "athloi run build", "jest": "jest -c jest.config.js", "test": "npm run lint && npm run jest", @@ -11,10 +11,7 @@ "blueprint": "node private/scripts/blueprint.js", "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", "start-storybook:ci": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com --ci --smoke-test", - "build-storybook": "build-storybook -o dist/storybook -s .storybook/static", "deploy-storybook:ci": "storybook-to-ghpages --ci --source-branch=main", - "start-docs": "(cd web && npm start)", - "build-docs": "(cd web && npm run build)", "heroku-postbuild": "make install && npm run build", "prepare": "npx snyk protect || npx snyk protect -d || true" }, From bd09eb925b757bf2ff73edef2b2d75c338c0b7b4 Mon Sep 17 00:00:00 2001 From: Rashedul Hassan <rashedul@outlook.com> Date: Thu, 10 Jun 2021 15:09:20 +0100 Subject: [PATCH 711/760] Add date-fns library ref to calculate cal days --- components/x-teaser/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/x-teaser/package.json b/components/x-teaser/package.json index 023a6e0b0..2099b8dff 100644 --- a/components/x-teaser/package.json +++ b/components/x-teaser/package.json @@ -18,7 +18,8 @@ "license": "ISC", "dependencies": { "@financial-times/x-engine": "file:../../packages/x-engine", - "dateformat": "^3.0.3" + "dateformat": "^3.0.3", + "date-fns": "^1.29.0" }, "devDependencies": { "@financial-times/x-rollup": "file:../../packages/x-rollup" From 2d09d499d0ae677fa1cbb6bf4f31badbeea1fdea Mon Sep 17 00:00:00 2001 From: Rashedul Hassan <rashedul@outlook.com> Date: Thu, 10 Jun 2021 15:10:31 +0100 Subject: [PATCH 712/760] Decide when to display Relative time vs Timestamp --- .../x-teaser/src/AlwaysShowTimestamp.jsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 components/x-teaser/src/AlwaysShowTimestamp.jsx diff --git a/components/x-teaser/src/AlwaysShowTimestamp.jsx b/components/x-teaser/src/AlwaysShowTimestamp.jsx new file mode 100644 index 000000000..939687988 --- /dev/null +++ b/components/x-teaser/src/AlwaysShowTimestamp.jsx @@ -0,0 +1,21 @@ +import { h } from '@financial-times/x-engine' +import TimeStamp from './TimeStamp' +import RelativeTime from './RelativeTime' +import { differenceInCalendarDays } from 'date-fns' +import { getDateOnly } from '../../x-teaser-timeline/src/lib/date' + +/** + * Timestamp shown always, the default 4h limit does not apply here + * If same calendar day, we show relative time e.g. X hours ago or Updated X min ago + * If different calendar day, we show full Date time e.g. June 9, 2021 + */ +export default (props) => { + const localTodayDate = getDateOnly(new Date().toISOString()) + const dateToCompare = getDateOnly(new Date(props.publishedDate).toISOString()) + + if (differenceInCalendarDays(localTodayDate, dateToCompare) >= 1) { + return <TimeStamp {...props} /> + } else { + return <RelativeTime {...props} override4hLimit={true} /> + } +} From d6307c6a357bfd4d306f8ee640743c07b6276985 Mon Sep 17 00:00:00 2001 From: Rashedul Hassan <rashedul@outlook.com> Date: Thu, 10 Jun 2021 15:10:58 +0100 Subject: [PATCH 713/760] Modify RelativeTime to override 4h limit --- components/x-teaser/src/RelativeTime.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/x-teaser/src/RelativeTime.jsx b/components/x-teaser/src/RelativeTime.jsx index 176c056c5..740283a16 100644 --- a/components/x-teaser/src/RelativeTime.jsx +++ b/components/x-teaser/src/RelativeTime.jsx @@ -15,17 +15,17 @@ const displayTime = (date) => { return `${hours} ${suffix}` } -export default ({ publishedDate, firstPublishedDate }) => { +export default ({ publishedDate, firstPublishedDate, override4hLimit = false }) => { const relativeDate = getRelativeDate(publishedDate) const status = getStatus(publishedDate, firstPublishedDate) - return isRecent(relativeDate) ? ( + return override4hLimit === true || isRecent(relativeDate) ? ( <div className={`o-teaser__timestamp o-teaser__timestamp--${status}`}> {status ? <span className="o-teaser__timestamp-prefix">{`${status} `} </span> : null} <time className="o-teaser__timestamp-date o-date" data-o-component="o-date" - data-o-date-format="time-ago-limit-4-hours" + data-o-date-format={override4hLimit ? null : 'time-ago-limit-4-hours'} dateTime={dateformat(publishedDate, dateformat.masks.isoDateTime, true)}> {/* Let o-date handle anything < 1 hour on the client */} {status ? '' : displayTime(relativeDate)} From e2b8635ae7789df679526e1e09db36e9a2b2d0be Mon Sep 17 00:00:00 2001 From: Rashedul Hassan <rashedul@outlook.com> Date: Thu, 10 Jun 2021 15:11:34 +0100 Subject: [PATCH 714/760] Add rendering logic to display timestamp always --- components/x-teaser/src/Status.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/x-teaser/src/Status.jsx b/components/x-teaser/src/Status.jsx index 97bd8a5cd..f23097294 100644 --- a/components/x-teaser/src/Status.jsx +++ b/components/x-teaser/src/Status.jsx @@ -2,6 +2,7 @@ import { h } from '@financial-times/x-engine' import TimeStamp from './TimeStamp' import RelativeTime from './RelativeTime' import LiveBlogStatus from './LiveBlogStatus' +import AlwaysShowTimestamp from './AlwaysShowTimestamp' export default (props) => { if (props.status) { @@ -9,7 +10,9 @@ export default (props) => { } if (props.publishedDate) { - if (props.useRelativeTime) { + if (props.alwaysDisplayTimestamp) { + return <AlwaysShowTimestamp {...props} /> + } else if (props.useRelativeTime) { return <RelativeTime {...props} /> } else { return <TimeStamp {...props} /> From 0b25f08a44e11f75ab01f6dab58ab71beeeae13e Mon Sep 17 00:00:00 2001 From: Rashedul Hassan <rashedul@outlook.com> Date: Fri, 11 Jun 2021 12:47:46 +0100 Subject: [PATCH 715/760] Remove getDateOnly() lib of x-teaser-timeline --- components/x-teaser/src/AlwaysShowTimestamp.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/x-teaser/src/AlwaysShowTimestamp.jsx b/components/x-teaser/src/AlwaysShowTimestamp.jsx index 939687988..4b4f32fe6 100644 --- a/components/x-teaser/src/AlwaysShowTimestamp.jsx +++ b/components/x-teaser/src/AlwaysShowTimestamp.jsx @@ -2,7 +2,6 @@ import { h } from '@financial-times/x-engine' import TimeStamp from './TimeStamp' import RelativeTime from './RelativeTime' import { differenceInCalendarDays } from 'date-fns' -import { getDateOnly } from '../../x-teaser-timeline/src/lib/date' /** * Timestamp shown always, the default 4h limit does not apply here @@ -10,8 +9,8 @@ import { getDateOnly } from '../../x-teaser-timeline/src/lib/date' * If different calendar day, we show full Date time e.g. June 9, 2021 */ export default (props) => { - const localTodayDate = getDateOnly(new Date().toISOString()) - const dateToCompare = getDateOnly(new Date(props.publishedDate).toISOString()) + const localTodayDate = new Date().toISOString().substr(0, 10) + const dateToCompare = new Date(props.publishedDate).toISOString().substr(0, 10) if (differenceInCalendarDays(localTodayDate, dateToCompare) >= 1) { return <TimeStamp {...props} /> From 815e9f8f0716188b89d7a35495990d6ab9a15e12 Mon Sep 17 00:00:00 2001 From: Rashedul Hassan <rashedul@outlook.com> Date: Mon, 14 Jun 2021 11:17:50 +0100 Subject: [PATCH 716/760] Change variable name for teaser prop --- components/x-teaser/src/AlwaysShowTimestamp.jsx | 2 +- components/x-teaser/src/Status.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/src/AlwaysShowTimestamp.jsx b/components/x-teaser/src/AlwaysShowTimestamp.jsx index 4b4f32fe6..8f7ed7755 100644 --- a/components/x-teaser/src/AlwaysShowTimestamp.jsx +++ b/components/x-teaser/src/AlwaysShowTimestamp.jsx @@ -9,7 +9,7 @@ import { differenceInCalendarDays } from 'date-fns' * If different calendar day, we show full Date time e.g. June 9, 2021 */ export default (props) => { - const localTodayDate = new Date().toISOString().substr(0, 10) + const localTodayDate = new Date().toISOString().substr(0, 10) // keep only the date bit const dateToCompare = new Date(props.publishedDate).toISOString().substr(0, 10) if (differenceInCalendarDays(localTodayDate, dateToCompare) >= 1) { diff --git a/components/x-teaser/src/Status.jsx b/components/x-teaser/src/Status.jsx index f23097294..7c3cc497a 100644 --- a/components/x-teaser/src/Status.jsx +++ b/components/x-teaser/src/Status.jsx @@ -10,7 +10,7 @@ export default (props) => { } if (props.publishedDate) { - if (props.alwaysDisplayTimestamp) { + if (props.useRelativeTimeIfToday) { return <AlwaysShowTimestamp {...props} /> } else if (props.useRelativeTime) { return <RelativeTime {...props} /> From c86089cac7838499454bde9f12fe83d010695169 Mon Sep 17 00:00:00 2001 From: Rashedul Hassan <rashedul@outlook.com> Date: Mon, 14 Jun 2021 11:22:41 +0100 Subject: [PATCH 717/760] updated flag name override4hLimit -> showAlways --- components/x-teaser/src/AlwaysShowTimestamp.jsx | 2 +- components/x-teaser/src/RelativeTime.jsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-teaser/src/AlwaysShowTimestamp.jsx b/components/x-teaser/src/AlwaysShowTimestamp.jsx index 8f7ed7755..a447c4fbc 100644 --- a/components/x-teaser/src/AlwaysShowTimestamp.jsx +++ b/components/x-teaser/src/AlwaysShowTimestamp.jsx @@ -15,6 +15,6 @@ export default (props) => { if (differenceInCalendarDays(localTodayDate, dateToCompare) >= 1) { return <TimeStamp {...props} /> } else { - return <RelativeTime {...props} override4hLimit={true} /> + return <RelativeTime {...props} showAlways={true} /> } } diff --git a/components/x-teaser/src/RelativeTime.jsx b/components/x-teaser/src/RelativeTime.jsx index 740283a16..ce9296f5f 100644 --- a/components/x-teaser/src/RelativeTime.jsx +++ b/components/x-teaser/src/RelativeTime.jsx @@ -15,17 +15,17 @@ const displayTime = (date) => { return `${hours} ${suffix}` } -export default ({ publishedDate, firstPublishedDate, override4hLimit = false }) => { +export default ({ publishedDate, firstPublishedDate, showAlways = false }) => { const relativeDate = getRelativeDate(publishedDate) const status = getStatus(publishedDate, firstPublishedDate) - return override4hLimit === true || isRecent(relativeDate) ? ( + return showAlways === true || isRecent(relativeDate) ? ( <div className={`o-teaser__timestamp o-teaser__timestamp--${status}`}> {status ? <span className="o-teaser__timestamp-prefix">{`${status} `} </span> : null} <time className="o-teaser__timestamp-date o-date" data-o-component="o-date" - data-o-date-format={override4hLimit ? null : 'time-ago-limit-4-hours'} + data-o-date-format={showAlways ? null : 'time-ago-limit-4-hours'} dateTime={dateformat(publishedDate, dateformat.masks.isoDateTime, true)}> {/* Let o-date handle anything < 1 hour on the client */} {status ? '' : displayTime(relativeDate)} From db17021a7207c4688b9f538cc2c681033562558f Mon Sep 17 00:00:00 2001 From: Rashedul Hassan <rashedul@outlook.com> Date: Thu, 17 Jun 2021 13:33:04 +0100 Subject: [PATCH 718/760] use new o-date format from o-date 4.2 --- components/x-teaser/src/RelativeTime.jsx | 2 +- components/x-teaser/storybook/index.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/src/RelativeTime.jsx b/components/x-teaser/src/RelativeTime.jsx index ce9296f5f..7d66b2aa8 100644 --- a/components/x-teaser/src/RelativeTime.jsx +++ b/components/x-teaser/src/RelativeTime.jsx @@ -25,7 +25,7 @@ export default ({ publishedDate, firstPublishedDate, showAlways = false }) => { <time className="o-teaser__timestamp-date o-date" data-o-component="o-date" - data-o-date-format={showAlways ? null : 'time-ago-limit-4-hours'} + data-o-date-format={showAlways ? 'time-ago-limit-24-hours' : 'time-ago-limit-4-hours'} dateTime={dateformat(publishedDate, dateformat.masks.isoDateTime, true)}> {/* Let o-date handle anything < 1 hour on the client */} {status ? '' : displayTime(relativeDate)} diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index 57b6ae430..10c3eb47c 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -3,7 +3,7 @@ import React from 'react' import BuildService from '../../../.storybook/build-service' const dependencies = { - 'o-date': '^4.0.0', + 'o-date': '^4.2.0', 'o-labels': '^5.0.0', 'o-normalise': '^2.0.0', 'o-teaser': '^4.0.0', From 2e8ad2140ba74f3757a1d1035fd058c6d78204df Mon Sep 17 00:00:00 2001 From: Max Bladen-Clark <max@buzzard.dev> Date: Thu, 24 Jun 2021 11:13:06 +0100 Subject: [PATCH 719/760] feat: Add support for Preact.Fragment in x-engine --- packages/x-engine/src/concerns/presets.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/x-engine/src/concerns/presets.js b/packages/x-engine/src/concerns/presets.js index 6be7124ef..493bf81a7 100644 --- a/packages/x-engine/src/concerns/presets.js +++ b/packages/x-engine/src/concerns/presets.js @@ -11,6 +11,7 @@ module.exports = { runtime: 'preact', factory: 'h', component: 'Component', + fragment: 'Fragment', render: 'render' }, hyperons: { From c3dd8a99c27827478da486821dcbf66424d69c17 Mon Sep 17 00:00:00 2001 From: Charlotte Payne <charlotte.payne@ft.com> Date: Thu, 1 Jul 2021 13:22:57 +0100 Subject: [PATCH 720/760] Add literal space before status text * o-labels and o-teaser have been updated and now have only 2px margin between the status dot and the status text. This does not match the design specification of 4px between the two elements. * Origami team requested that x-teaser be updated with the leading space rather than o-labels because all consumes of o-labels are not known. * References: Design spec - https://www.figma.com/file/MyHQ1qdwYyek5IBdhEEaND/FT-UI-Library?node-id=318%3A816 Updated o-date with reduced space: https://github.com/Financial-Times/o-labels/blob/master/src/scss/_placeholders.scss#L48-L63 --- components/x-teaser/src/RelativeTime.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/src/RelativeTime.jsx b/components/x-teaser/src/RelativeTime.jsx index 7d66b2aa8..6efd9911e 100644 --- a/components/x-teaser/src/RelativeTime.jsx +++ b/components/x-teaser/src/RelativeTime.jsx @@ -21,7 +21,7 @@ export default ({ publishedDate, firstPublishedDate, showAlways = false }) => { return showAlways === true || isRecent(relativeDate) ? ( <div className={`o-teaser__timestamp o-teaser__timestamp--${status}`}> - {status ? <span className="o-teaser__timestamp-prefix">{`${status} `} </span> : null} + {status ? <span className="o-teaser__timestamp-prefix">{` ${status} `} </span> : null} <time className="o-teaser__timestamp-date o-date" data-o-component="o-date" From 93b5940e2d0509c0adff73adffee6e76f5b44b63 Mon Sep 17 00:00:00 2001 From: Charlotte Payne <charlotte.payne@ft.com> Date: Thu, 1 Jul 2021 13:25:35 +0100 Subject: [PATCH 721/760] Update storybook dependencies * These newer versions of o-labels and o-teaser mean the spacing between the status dot and status text in the relative time stamp is now 2px margin-right. x-teaser accomodates this with a leading literal space before the status text. --- components/x-teaser/storybook/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-teaser/storybook/index.jsx b/components/x-teaser/storybook/index.jsx index 10c3eb47c..23cc13a35 100644 --- a/components/x-teaser/storybook/index.jsx +++ b/components/x-teaser/storybook/index.jsx @@ -4,9 +4,9 @@ import BuildService from '../../../.storybook/build-service' const dependencies = { 'o-date': '^4.2.0', - 'o-labels': '^5.0.0', + 'o-labels': '^5.2.0', 'o-normalise': '^2.0.0', - 'o-teaser': '^4.0.0', + 'o-teaser': '^5.2.3', 'o-typography': '^6.0.0', 'o-video': '^6.0.0' } From 0f516193f9f0e173da77315d591e88fd6b3b7368 Mon Sep 17 00:00:00 2001 From: Charlotte Payne <charlotte.payne@ft.com> Date: Thu, 1 Jul 2021 15:47:56 +0100 Subject: [PATCH 722/760] Add literal leading space before live blog status * Mirror the change to the relative timestamp component * o-labels and o-teaser have been updated and now have only 2px margin between the status dot and the status text. This does not match the design specification of 4px between the two elements. * Origami team requested that x-teaser be updated with the leading space rather than o-labels because all consumes of o-labels are not known. * References: Design spec - https://www.figma.com/file/MyHQ1qdwYyek5IBdhEEaND/FT-UI-Library?node-id=318%3A816 Updated o-date with reduced space: https://github.com/Financial-Times/o-labels/blob/master/src/scss/_placeholders.scss#L48-L63 --- components/x-teaser/src/LiveBlogStatus.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-teaser/src/LiveBlogStatus.jsx b/components/x-teaser/src/LiveBlogStatus.jsx index c6447eb28..a8221b734 100644 --- a/components/x-teaser/src/LiveBlogStatus.jsx +++ b/components/x-teaser/src/LiveBlogStatus.jsx @@ -15,6 +15,6 @@ const LiveBlogModifiers = { export default ({ status }) => status && status !== 'closed' ? ( <div className={`o-teaser__timestamp o-teaser__timestamp--${LiveBlogModifiers[status]}`}> - <span className="o-teaser__timestamp-prefix">{LiveBlogLabels[status]}</span> + <span className="o-teaser__timestamp-prefix">{` ${LiveBlogLabels[status]} `}</span> </div> ) : null From ef7e596afe556d1f3b30aa3a6ea07d65b934d165 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Thu, 1 Jul 2021 08:45:15 +0100 Subject: [PATCH 723/760] Fix gift article storybook to call activate action --- components/x-gift-article/storybook/free-article.js | 10 ++++------ components/x-gift-article/storybook/index.jsx | 10 +++++----- components/x-gift-article/storybook/native-share.js | 10 ++++------ .../x-gift-article/storybook/with-gift-credits.js | 10 ++++------ .../x-gift-article/storybook/without-gift-credits.js | 10 ++++------ 5 files changed, 21 insertions(+), 29 deletions(-) diff --git a/components/x-gift-article/storybook/free-article.js b/components/x-gift-article/storybook/free-article.js index f4bb729ac..718ff1bb4 100644 --- a/components/x-gift-article/storybook/free-article.js +++ b/components/x-gift-article/storybook/free-article.js @@ -19,12 +19,10 @@ exports.fetchMock = (fetchMock) => { fetchMock .restore() .get('/article/gift-credits', { - credits: { - allowance: 20, - consumedCredits: 5, - remainingCredits: 15, - renewalDate: '2018-08-01T00:00:00Z' - } + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' }) .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { shortenedUrl: 'https://shortened-non-gift-url' diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index c42641316..c9abcdc2d 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -20,7 +20,7 @@ export const WithGiftCredits = (args) => { <Helmet> <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> </Helmet> - <GiftArticle {...args} /> + <GiftArticle {...args} actionsRef={(actions) => actions?.activate()} /> </div> ) } @@ -35,7 +35,7 @@ export const WithoutGiftCredits = (args) => { <Helmet> <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> </Helmet> - <GiftArticle {...args} /> + <GiftArticle {...args} actionsRef={(actions) => actions?.activate()} /> </div> ) } @@ -67,7 +67,7 @@ export const FreeArticle = (args) => { <Helmet> <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> </Helmet> - <GiftArticle {...args} /> + <GiftArticle {...args} actionsRef={(actions) => actions?.activate()} /> </div> ) } @@ -83,7 +83,7 @@ export const NativeShare = (args) => { <Helmet> <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> </Helmet> - <GiftArticle {...args} /> + <GiftArticle {...args} actionsRef={(actions) => actions?.activate()} /> </div> ) } @@ -99,7 +99,7 @@ export const ErrorResponse = (args) => { <Helmet> <link rel="stylesheet" href={`components/x-gift-article/dist/GiftArticle.css`} /> </Helmet> - <GiftArticle {...args} /> + <GiftArticle {...args} actionsRef={(actions) => actions?.activate()} /> </div> ) } diff --git a/components/x-gift-article/storybook/native-share.js b/components/x-gift-article/storybook/native-share.js index 679d714fe..68ff177ac 100644 --- a/components/x-gift-article/storybook/native-share.js +++ b/components/x-gift-article/storybook/native-share.js @@ -23,12 +23,10 @@ exports.fetchMock = (fetchMock) => { fetchMock .restore() .get('/article/gift-credits', { - credits: { - allowance: 20, - consumedCredits: 2, - remainingCredits: 18, - renewalDate: '2018-08-01T00:00:00Z' - } + allowance: 20, + consumedCredits: 2, + remainingCredits: 18, + renewalDate: '2018-08-01T00:00:00Z' }) .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { shortenedUrl: 'https://shortened-gift-url' diff --git a/components/x-gift-article/storybook/with-gift-credits.js b/components/x-gift-article/storybook/with-gift-credits.js index 371888035..3f1ed3b65 100644 --- a/components/x-gift-article/storybook/with-gift-credits.js +++ b/components/x-gift-article/storybook/with-gift-credits.js @@ -23,12 +23,10 @@ exports.fetchMock = (fetchMock) => { fetchMock .restore() .get('/article/gift-credits', { - credits: { - allowance: 20, - consumedCredits: 5, - remainingCredits: 15, - renewalDate: '2018-08-01T00:00:00Z' - } + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' }) .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { shortenedUrl: 'https://shortened-gift-url' diff --git a/components/x-gift-article/storybook/without-gift-credits.js b/components/x-gift-article/storybook/without-gift-credits.js index 1b9f9dd67..7fdd9149c 100644 --- a/components/x-gift-article/storybook/without-gift-credits.js +++ b/components/x-gift-article/storybook/without-gift-credits.js @@ -19,12 +19,10 @@ exports.fetchMock = (fetchMock) => { fetchMock .restore() .get('/article/gift-credits', { - credits: { - allowance: 20, - consumedCredits: 20, - remainingCredits: 0, - renewalDate: '2018-08-01T00:00:00Z' - } + allowance: 20, + consumedCredits: 20, + remainingCredits: 0, + renewalDate: '2018-08-01T00:00:00Z' }) .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { shortenedUrl: 'https://shortened-non-gift-url' From b9858d0e9b087068ecbffa54e7edf1e53791ee66 Mon Sep 17 00:00:00 2001 From: "Asuka.Ochi" <asuka.ochi@ft.com> Date: Fri, 13 Aug 2021 17:37:54 +0100 Subject: [PATCH 724/760] Apply focus outline depends on theme colour --- .../src/styles/mixins/lozenge/_themes.scss | 12 +++-- .../src/styles/mixins/lozenge/main.scss | 53 +++++++++++++++++-- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss b/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss index c144e52ee..72af64738 100644 --- a/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss +++ b/components/x-follow-button/src/styles/mixins/lozenge/_themes.scss @@ -6,28 +6,32 @@ $myft-lozenge-themes: ( text: oColorsByName('white'), highlight: oColorsByName('claret-50'), pressed-highlight: rgba(oColorsByName('black'), 0.05), - disabled: rgba(oColorsByName('black'), 0.5) + disabled: rgba(oColorsByName('black'), 0.5), + focus-outline: oColorsByUsecase('focus', 'outline', $fallback: null) ), inverse: ( background: oColorsByName('white'), text: oColorsByName('claret'), highlight: rgba(white, 0.8), pressed-highlight: rgba(white, 0.2), - disabled: rgba(oColorsByName('white'), 0.5) + disabled: rgba(oColorsByName('white'), 0.5), + focus-outline: oColorsByName('white') ), opinion: ( background: oColorsByName('oxford-40'), text: oColorsByName('white'), highlight: oColorsByName('oxford-30'), pressed-highlight: rgba(oColorsByName('oxford-40'), 0.2), - disabled: rgba(oColorsByName('black'), 0.5) + disabled: rgba(oColorsByName('black'), 0.5), + focus-outline: oColorsByUsecase('focus', 'outline', $fallback: null) ), monochrome: ( background: oColorsByName('white'), text: oColorsByName('black'), highlight: oColorsByName('white-80'), pressed-highlight: rgba(oColorsByName('white'), 0.2), - disabled: rgba(oColorsByName('white'), 0.5) + disabled: rgba(oColorsByName('white'), 0.5), + focus-outline: oColorsByName('white') ) ); diff --git a/components/x-follow-button/src/styles/mixins/lozenge/main.scss b/components/x-follow-button/src/styles/mixins/lozenge/main.scss index c0a0d854a..6ef420cc0 100644 --- a/components/x-follow-button/src/styles/mixins/lozenge/main.scss +++ b/components/x-follow-button/src/styles/mixins/lozenge/main.scss @@ -1,6 +1,53 @@ @import './themes'; @import './toggle-icon'; +@mixin focusOutlineColor($focus-color) { + // Apply :focus styles as a fallback + // These styles will be applied to all browsers that don't use the polyfill, this includes browsers which support the feature natively. + :global(body:not(.js-focus-visible)) &, + :global(html:not(.js-focus-visible)) & { + // Standardise focus styles. + &:focus { + outline: 2px solid $focus-color; + } + } + + // When the focus-visible polyfill is applied `.js-focus-visible` is added to the html dom node + // (the body node in v4 of the 3rd party polyfill) + + // stylelint-disable-next-line selector-no-qualifying-type + :global(body.js-focus-visible) &, // stylelint-disable-next-line selector-no-qualifying-type + :global(html.js-focus-visible) & { + // Standardise focus styles. + // stylelint-disable-next-line selector-no-qualifying-type + &:global(.focus-visible) { + outline: 2px solid $focus-color; + } + // Disable browser default focus style. + // stylelint-disable-next-line selector-no-qualifying-type + &:focus:not(:global(.focus-visible)) { + outline: 0; + } + } + + // These styles will be ignored by browsers which do not recognise the :focus-visible selector (as per the third bullet point in https://www.w3.org/TR/selectors-3/#Conformance) + // If a browser supports :focus-visible we unset the :focus styles that were applied above + // (within the html:not(.js-focus-visible) block). + &:focus-visible, + :global(body:not(.js-focus-visible)) &:focus, + :global(html:not(.js-focus-visible)) &:focus { + outline: unset; + } + + // Styles given :focus-visible support. Extra selectors needed to match + // previous `:focus` selector specificity. + :global(body:not(.js-focus-visible)) &:focus-visible, + :global(html:not(.js-focus-visible)) &:focus-visible, + &:focus-visible { + outline: 2px solid $focus-color; + } +} + @mixin myftLozengeTheme($theme: standard, $with-toggle-icon: false) { @if $with-toggle-icon != false { @include myftToggleIcon($theme); @@ -11,6 +58,8 @@ border: 1px solid getThemeColor(background); color: getThemeColor(background); + @include focusOutlineColor(getThemeColor(focus-outline)) + &:hover, &:focus { background-color: getThemeColor(pressed-highlight); @@ -57,8 +106,4 @@ text-overflow: ellipsis; transition: border-color, background-color 0.5s ease; white-space: nowrap; - - &:focus { - outline: none; - } } From fb325a00ac42ce89274038efde1bd9da655dbc7a Mon Sep 17 00:00:00 2001 From: gyss <gravedian@gmail.com> Date: Fri, 3 Sep 2021 10:41:10 +0100 Subject: [PATCH 725/760] fix accessibility issue since aria-label in span is not correctly announced when placed on a span --- components/x-teaser/src/Title.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/x-teaser/src/Title.jsx b/components/x-teaser/src/Title.jsx index 42d8f73d5..1a7a0495e 100644 --- a/components/x-teaser/src/Title.jsx +++ b/components/x-teaser/src/Title.jsx @@ -28,9 +28,8 @@ export default ({ title, altTitle, headlineTesting, relativeUrl, url, indicators {indicators && indicators.accessLevel === 'premium' ? ( <span> {' '} - <span className={premiumClass} aria-label="Premium content"> - Premium - </span> + <span className={premiumClass}>Premium</span> + <span className="o-normalise-visually-hidden"> content</span> </span> ) : null} </div> From fbc15ef25eb4c2c05657ac830c3cbfab359428b5 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Wed, 8 Sep 2021 12:05:57 +0100 Subject: [PATCH 726/760] changed live-blog-post title tag to h2 --- components/x-live-blog-post/src/LiveBlogPost.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index e7b809f43..25c081b36 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -31,7 +31,7 @@ const LiveBlogPost = (props) => { <Timestamp publishedTimestamp={publishedDate || publishedTimestamp} /> </div> {showBreakingNewsLabel && <div className={styles['live-blog-post__breaking-news']}>Breaking news</div>} - {title && <h1 className={styles['live-blog-post__title']}>{title}</h1>} + {title && <h2 className={styles['live-blog-post__title']}>{title}</h2>} {byline && <p className={styles['live-blog-post__byline']}>{byline}</p>} <div className={`${styles['live-blog-post__body']} n-content-body article--body`} From 7646ad23e270cc3db740288aace9937ea5d7dea6 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Wed, 8 Sep 2021 12:24:10 +0100 Subject: [PATCH 727/760] updated liveblogpost.jsx test --- .../x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index 28cab6c29..b038c90fe 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -54,7 +54,7 @@ describe('x-live-blog-post', () => { const liveBlogPost = mount(<LiveBlogPost {...regularPostSpark} />) expect(liveBlogPost.html()).toContain('Test title') - expect(liveBlogPost.html()).toContain('</h1>') + expect(liveBlogPost.html()).toContain('</h2>') }) }) @@ -68,7 +68,7 @@ describe('x-live-blog-post', () => { it('skips rendering of the title', () => { const liveBlogPost = mount(<LiveBlogPost {...postWithoutTitle} />) - expect(liveBlogPost.html()).not.toContain('</h1>') + expect(liveBlogPost.html()).not.toContain('</h2>') }) }) @@ -115,7 +115,7 @@ describe('x-live-blog-post', () => { const liveBlogPost = mount(<LiveBlogPost {...regularPostWordpress} />) expect(liveBlogPost.html()).toContain('Test title') - expect(liveBlogPost.html()).toContain('</h1>') + expect(liveBlogPost.html()).toContain('</h2>') }) }) @@ -129,7 +129,7 @@ describe('x-live-blog-post', () => { it('skips rendering of the title', () => { const liveBlogPost = mount(<LiveBlogPost {...postWithoutTitle} />) - expect(liveBlogPost.html()).not.toContain('</h1>') + expect(liveBlogPost.html()).not.toContain('</h2>') }) }) From 0c17c643d538802177a6f96d5d4d00a14ed2e0ad Mon Sep 17 00:00:00 2001 From: circle-auto-deploy <estefania.morton@ft.com> Date: Mon, 13 Sep 2021 13:13:05 +0100 Subject: [PATCH 728/760] using renovate-config-next-beta --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 639444f58..2bde8702b 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,5 @@ { "extends": [ - "github>financial-times/renovate-config-next" + "github>financial-times/renovate-config-next-beta" ] } From b1a31232fbd112da43188a80eb7e9a3e187c2c3e Mon Sep 17 00:00:00 2001 From: Serena <serena.chan@ft.com> Date: Mon, 20 Sep 2021 13:07:20 +0100 Subject: [PATCH 729/760] Added tests for register-component.js --- .../__tests__/registerComponent.test.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 components/x-interaction/__tests__/registerComponent.test.js diff --git a/components/x-interaction/__tests__/registerComponent.test.js b/components/x-interaction/__tests__/registerComponent.test.js new file mode 100644 index 000000000..77799cbc4 --- /dev/null +++ b/components/x-interaction/__tests__/registerComponent.test.js @@ -0,0 +1,62 @@ +const { + registerComponent, + getComponentByName, + getComponent, + getComponentName +} = require('../src/concerns/register-component') +const { withActions } = require('../') + +describe('registerComponent', () => { + let name + let Component + beforeAll(() => { + name = 'testComponent' + Component = withActions({})(() => null) + }) + + it(`should register a component in registerComponent`, () => { + registerComponent(Component, name) + + const actualComponent = getComponentByName(name) + expect(actualComponent).toBeTruthy() + }) + + it('should throw an error if the component has already been registered', () => { + expect(() => registerComponent(Component, name)).toThrow( + 'x-interaction a component has already been registered under that name, please use another name.' + ) + }) + + it('should throw an error if the component is not x-interaction wrapped', () => { + const unwrappedComponentName = 'unwrappedComponent' + const unwrappedComponent = { _wraps: null } + + expect(() => registerComponent(unwrappedComponent, unwrappedComponentName)).toThrow( + 'only x-interaction wrapped components (i.e. the component returned from withActions) can be registered' + ) + }) + + it('should get component that is already registered in getComponent', () => { + expect(getComponent(Component)).toBeTruthy() + }) + + it('should get component by name in getComponentByName', () => { + const actualComponent = getComponentByName(name) + + expect(actualComponent).toEqual(Component) + }) + + it('should get component name in getComponentName', () => { + const actualName = getComponentName(Component) + + expect(actualName).toBe(name) + }) + + it('should return Unknown if Component is not registered in getComponentName', () => { + const unregisteredComponent = withActions({})(() => null) + + const actualName = getComponentName(unregisteredComponent) + + expect(actualName).toBe('Unknown') + }) +}) From bab58c11f5ab5a66f7b6d0adda902d5b7355827f Mon Sep 17 00:00:00 2001 From: Serena <serena.chan@ft.com> Date: Tue, 21 Sep 2021 18:08:57 +0100 Subject: [PATCH 730/760] Added tests for serialiser.js --- .../__tests__/serialiser.test.js | 70 +++++++++++++++++++ components/x-interaction/readme.md | 3 + 2 files changed, 73 insertions(+) create mode 100644 components/x-interaction/__tests__/serialiser.test.js diff --git a/components/x-interaction/__tests__/serialiser.test.js b/components/x-interaction/__tests__/serialiser.test.js new file mode 100644 index 000000000..ab91780a9 --- /dev/null +++ b/components/x-interaction/__tests__/serialiser.test.js @@ -0,0 +1,70 @@ +import { Serialiser } from '../src/concerns/serialiser' +import * as registerComponent from '../src/concerns/register-component' +const { withActions } = require('../') +const xEngine = require('@financial-times/x-engine') + +describe('serialiser', () => { + let serialiser + let Component + let name + beforeAll(() => { + serialiser = new Serialiser() + name = 'testComponent' + Component = withActions({})(() => null) + }) + + it('pushes Component to data array in addData', () => { + jest.spyOn(registerComponent, 'getComponent').mockReturnValue(Component) + jest.spyOn(registerComponent, 'getComponentName').mockReturnValue(name) + + serialiser.addData('id', Component, {}) + + expect(serialiser.data.length).toEqual(1) + }) + + it('throws Error if component is not registered in addData', () => { + jest.spyOn(registerComponent, 'getComponent').mockReturnValue(undefined) + + expect(() => serialiser.addData('id', Component, {})).toThrow( + `a Serialiser's addData was called for an unregistered component. ensure you're registering your component before attempting to output the hydration data` + ) + }) + + it('throws Error if serialiser is destroyed in addData', () => { + serialiser.destroyed = true + jest.spyOn(registerComponent, 'getComponent').mockReturnValue(Component) + + expect(() => serialiser.addData('id', Component, {})).toThrow( + `an interaction component was rendered after flushHydrationData was called. ensure you're outputting the hydration data after rendering every component` + ) + }) + + it('throws Error if serialiser is destroyed in flushHydrationData', () => { + serialiser.destroyed = true + expect(() => serialiser.flushHydrationData()).toThrow( + `a Serialiser's flushHydrationData was called twice. ensure you're not reusing a Serialiser between requests` + ) + }) + + it('returns data and sets destroyed to true if serialiser is not destroyed in flushHydrationData', () => { + serialiser.destroyed = false + serialiser.data = [{ id: 'id', component: Component, props: '' }] + + const data = serialiser.flushHydrationData() + + expect(data).toEqual(serialiser.data) + expect(serialiser.destroyed).toEqual(true) + }) + + it('renders the hydration data in outputHydrationData', () => { + const expectedData = {} + jest.spyOn(xEngine, 'h').mockReturnValue('target') + jest.spyOn(xEngine, 'render').mockReturnValue(expectedData) + + const data = serialiser.outputHydrationData() + + expect(xEngine.render).toHaveBeenCalled() + expect(xEngine.h).toHaveBeenCalled() + expect(data).toEqual(expectedData) + }) +}) diff --git a/components/x-interaction/readme.md b/components/x-interaction/readme.md index f29ec140f..9c7f805f2 100644 --- a/components/x-interaction/readme.md +++ b/components/x-interaction/readme.md @@ -111,6 +111,9 @@ export const Greeting = greetingActions(BaseGreeting); ### Hydrating server-rendered markup +[Hydration](https://en.wikipedia.org/wiki/Hydration_(web_development)#:~:text=In%20web%20development%2C%20hydration%20or,handlers%20to%20the%20HTML%20elements. +): a technique in which client-side JavaScript converts a static HTML web page done by server-side rendering, into a dynamic web page by attaching event handlers to the HTML elements. + When you have an `x-interaction` component rendered by the server, and you want to attach the client-side version of the component to handle the actions, rather than rendering the component manually (which might become unwieldy, especially if you have many components & instances on the page), you can have `x-interaction` manage it for you. There are three parts to this: registering the component, serialising and hydrating. From cf0d503c3457ba5ef0a7a051f68771acf656e9ec Mon Sep 17 00:00:00 2001 From: Alessandro Vito <alessandro.vito84@gmail.com> Date: Thu, 23 Sep 2021 17:37:40 +0100 Subject: [PATCH 731/760] ADSDEV-897 change outline color for Allow/Block buttons --- components/x-privacy-manager/src/components/radio-btn.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/x-privacy-manager/src/components/radio-btn.scss b/components/x-privacy-manager/src/components/radio-btn.scss index f3cbf02fe..5100130c1 100644 --- a/components/x-privacy-manager/src/components/radio-btn.scss +++ b/components/x-privacy-manager/src/components/radio-btn.scss @@ -46,8 +46,7 @@ $transitionDuration: 0.1s; // Since <input> itself is hidden, apply a familiar focus style to the visible <label> // As adjacent siblings we can reflect the <input>'s focus state here .input:focus + & { - outline: 2px solid #7aacfe; - outline: 5px auto -webkit-focus-ring-color; + outline: 2px solid oColorsByName('teal-100'); background-color: oColorsByName('teal-40'); } From e07f876798f7aa08ae9fe764400ab57199e170bf Mon Sep 17 00:00:00 2001 From: Alessandro Vito <alessandro.vito84@gmail.com> Date: Thu, 23 Sep 2021 17:38:55 +0100 Subject: [PATCH 732/760] ADSDEV-898 change outline color for Save button --- components/x-privacy-manager/src/privacy-manager.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/x-privacy-manager/src/privacy-manager.scss b/components/x-privacy-manager/src/privacy-manager.scss index 7b2f756f0..662951d7b 100644 --- a/components/x-privacy-manager/src/privacy-manager.scss +++ b/components/x-privacy-manager/src/privacy-manager.scss @@ -72,4 +72,8 @@ display: block; margin: oSpacingByName(s8) auto 0; padding: 0 oSpacingByName(m12); + + &:focus-visible { + outline: 2px solid oColorsByName('teal-100'); + } } From 4a48ddb850d82d65b14fdf59930cf5ef29e8500f Mon Sep 17 00:00:00 2001 From: Serena <serena.chan@ft.com> Date: Fri, 24 Sep 2021 18:11:33 +0100 Subject: [PATCH 733/760] Added e2e test for hydration and serialisation Fixed test hanging and cleaned code up --- .circleci/config.yml | 13 ++++++- .eslintignore | 1 + .gitignore | 3 +- components/x-interaction/readme.md | 2 +- e2e/app.js | 5 +++ e2e/common.js | 21 ++++++++++ e2e/e2e.test.js | 62 ++++++++++++++++++++++++++++++ e2e/index.js | 4 ++ e2e/jest.e2e.config.js | 11 ++++++ e2e/package.json | 43 +++++++++++++++++++++ e2e/webpack.config.js | 34 ++++++++++++++++ jest.config.js | 3 +- package.json | 4 +- 13 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 e2e/app.js create mode 100644 e2e/common.js create mode 100644 e2e/e2e.test.js create mode 100644 e2e/index.js create mode 100644 e2e/jest.e2e.config.js create mode 100644 e2e/package.json create mode 100644 e2e/webpack.config.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 817aa5569..c38b41685 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ references: container_config_node: &container_config_node working_directory: ~/project/build docker: - - image: circleci/node:12 + - image: circleci/node:12-browsers workspace_root: &workspace_root ~/project @@ -105,6 +105,14 @@ jobs: - run: name: Run storybook command: npm run start-storybook:ci + + e2e-test: + <<: *container_config_node + steps: + - *attach_workspace + - run: + name: Run end to end test + command: npm run e2e publish: <<: *container_config_node @@ -164,6 +172,9 @@ workflows: - test: requires: - build + - e2e-test: + requires: + - build - deploy: filters: <<: *filters_only_main diff --git a/.eslintignore b/.eslintignore index 114afbe73..0fe84d9ab 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,3 +8,4 @@ **/public-prod/** **/blueprints/** web/static/** +/e2e/** \ No newline at end of file diff --git a/.gitignore b/.gitignore index c0be6db50..78ec717b3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ bower_components npm-debug.log .DS_Store dist -.idea \ No newline at end of file +.idea +coverage \ No newline at end of file diff --git a/components/x-interaction/readme.md b/components/x-interaction/readme.md index 9c7f805f2..b7c614c7a 100644 --- a/components/x-interaction/readme.md +++ b/components/x-interaction/readme.md @@ -179,7 +179,7 @@ When rendered on the server side, components output an extra wrapper element, wi `x-interaction` exports a function `hydrate`. This should be called on the client side. It inspects the global serialisation data on the page, uses the identifiers to find the wrapper elements, and calls `render` from your chosen `x-engine` client-side runtime to render component instances into the wrappers. -Before calling `hydrate`, you must first `import` any `x-interaction` components that will be rendered on the page. The components register themselves with the `x-interaction` runtime when imported; you don't need to do anything with the imported component. This will also ensure the component is included in your client-side bundle. +Before calling `hydrate`, you must first `import` any `x-interaction` components that will be rendered on the page. The components register themselves with the `x-interaction` runtime when imported; you don't need to do anything with the imported component. This will also ensure the component is included in your client-side bundle. Similarly if the component that you're server side rendering is just a component that you've created through `withActions`, make sure you import that component along with its registerComponent invokation. Because `hydrate` expects the wrappers to be present in the DOM when called, it should be called after [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded). Depending on your page structure, it might be appropriate to hydrate the component when it's scrolled into view. diff --git a/e2e/app.js b/e2e/app.js new file mode 100644 index 000000000..28e873a04 --- /dev/null +++ b/e2e/app.js @@ -0,0 +1,5 @@ +// set up app to host main.js file included in server side rendered html +const express = require('express') +const server = express() +server.use(express.static(__dirname)) +exports.app = server diff --git a/e2e/common.js b/e2e/common.js new file mode 100644 index 000000000..d2af05a15 --- /dev/null +++ b/e2e/common.js @@ -0,0 +1,21 @@ +const { withActions, registerComponent } = require('@financial-times/x-interaction') +const { h } = require('@financial-times/x-engine') + +export const greetingActions = withActions({ + actionOne() { + return { greeting: 'world' } + } +}) + +export const GreetingComponent = greetingActions(({ greeting, actions }) => { + return ( + <div className="greeting-text"> + hello {greeting} + <button className="greeting-button" onClick={actions.actionOne}> + click to add to hello + </button> + </div> + ) +}) + +registerComponent(GreetingComponent, 'GreetingComponent') diff --git a/e2e/e2e.test.js b/e2e/e2e.test.js new file mode 100644 index 000000000..c95ac15f3 --- /dev/null +++ b/e2e/e2e.test.js @@ -0,0 +1,62 @@ +/** + * @jest-environment node + */ + +const { h } = require('@financial-times/x-engine') // required for <GreetingComponent> +const { Serialiser, HydrationData } = require('@financial-times/x-interaction') +const puppeteer = require('puppeteer') +const ReactDOMServer = require('react-dom/server') +const express = require('express') +import React from 'react' +import { GreetingComponent } from './common' + +describe('x-interaction-e2e', () => { + let browser + let page + let app + let server + + beforeAll(async () => { + app = express() + server = app.listen(3004) + app.use(express.static(__dirname)) + browser = await puppeteer.launch() + page = await browser.newPage() + }) + + it('attaches the event listener to SSR components on hydration', async () => { + const ClientComponent = () => { + // main.js is the transpiled version of index.js, which contains the registered GreetingComponent, and invokes hydrate + return <script type="module" src="./main.js" charset="utf-8"></script> + } + + const serialiser = new Serialiser() + const htmlString = ReactDOMServer.renderToString( + <> + <GreetingComponent serialiser={serialiser} /> + <HydrationData serialiser={serialiser} /> + <ClientComponent /> + </> + ) + + app.get('/', (req, res) => { + res.send(htmlString) + }) + + // go to page and click button + await page.goto('http://localhost:3004') + await page.waitForSelector('.greeting-button') + await page.click('.greeting-button') + const text = await page.$eval('.greeting-text', (e) => e.textContent) + expect(text).toContain('hello world') + }) + + afterAll(async () => { + try { + ;(await browser) && browser.close() + await server.close() + } catch (e) { + console.log(e) + } + }) +}) diff --git a/e2e/index.js b/e2e/index.js new file mode 100644 index 000000000..a4c4f180f --- /dev/null +++ b/e2e/index.js @@ -0,0 +1,4 @@ +import { hydrate } from '@financial-times/x-interaction' +import './common' + +document.addEventListener('DOMContentLoaded', hydrate) diff --git a/e2e/jest.e2e.config.js b/e2e/jest.e2e.config.js new file mode 100644 index 000000000..12b5a78a7 --- /dev/null +++ b/e2e/jest.e2e.config.js @@ -0,0 +1,11 @@ +module.exports = { + testMatch: ['<rootDir>/e2e.test.js'], + testPathIgnorePatterns: ['/node_modules/', '/bower_components/'], + transform: { + '^.+\\.jsx?$': '../packages/x-babel-config/jest' + }, + moduleNameMapper: { + '^[./a-zA-Z0-9$_-]+\\.scss$': '<rootDir>/__mocks__/styleMock.js' + }, + testEnvironment: 'node' +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 000000000..d11824a31 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,43 @@ +{ + "name": "x-dash-e2e", + "version": "0.0.0", + "private": "true", + "description": "This module enables you to write x-dash components that respond to events and change their own data.", + "keywords": [ + "x-dash" + ], + "author": "", + "license": "ISC", + "x-dash": { + "engine": { + "server": { + "runtime": "react", + "factory": "createElement", + "component": "Component", + "fragment": "Fragment", + "renderModule": "react-dom/server", + "render": "renderToStaticMarkup" + }, + "browser": "react" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/Financial-Times/x-dash.git" + }, + "engines": { + "node": "12.x" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "puppeteer": "^10.4.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "webpack": "^5.54.0", + "webpack-cli": "^4.8.0", + "@financial-times/x-engine": "file:../packages/x-engine", + "@financial-times/x-interaction": "file:../components/x-interaction" + } +} diff --git a/e2e/webpack.config.js b/e2e/webpack.config.js new file mode 100644 index 000000000..3915d2f09 --- /dev/null +++ b/e2e/webpack.config.js @@ -0,0 +1,34 @@ +const path = require('path') +const xEngine = require('../packages/x-engine/src/webpack') +const webpack = require('webpack') + +module.exports = { + entry: './index.js', + output: { + filename: 'main.js', + path: path.resolve(__dirname) + }, + plugins: [ + new webpack.ProvidePlugin({ + React: 'react' + }), + xEngine() + ], + module: { + rules: [ + { + test: /\.(js|jsx)$/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env', '@babel/preset-react'] + } + }, + exclude: /node_modules/ + } + ] + }, + resolve: { + extensions: ['.js', '.jsx', '.json', '.wasm', '.mjs', '*'] + } +} diff --git a/jest.config.js b/jest.config.js index 164aa925f..108438432 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,5 +7,6 @@ module.exports = { }, moduleNameMapper: { '^[./a-zA-Z0-9$_-]+\\.scss$': '<rootDir>/__mocks__/styleMock.js' - } + }, + modulePathIgnorePatterns: ['<rootDir>/e2e/'] } diff --git a/package.json b/package.json index 17d5013c7..8ae3e7a81 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build-only": "athloi run build", "jest": "jest -c jest.config.js", "test": "npm run lint && npm run jest", + "e2e": "cd e2e && ./node_modules/.bin/webpack && jest -c jest.e2e.config.js", "lint": "eslint . --ext=js,jsx", "blueprint": "node private/scripts/blueprint.js", "start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com", @@ -72,6 +73,7 @@ "workspaces": [ "components/*", "packages/*", - "tools/*" + "tools/*", + "e2e" ] } From 5188997f0a869375649509efbfbbaab8cca1401b Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Mon, 4 Oct 2021 23:22:56 +0100 Subject: [PATCH 734/760] adding width and height restrictions to radio input for privacy manager --- components/x-privacy-manager/src/components/radio-btn.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/x-privacy-manager/src/components/radio-btn.scss b/components/x-privacy-manager/src/components/radio-btn.scss index 5100130c1..d4609320a 100644 --- a/components/x-privacy-manager/src/components/radio-btn.scss +++ b/components/x-privacy-manager/src/components/radio-btn.scss @@ -8,6 +8,8 @@ $transitionDuration: 0.1s; .input { @include oNormaliseVisuallyHidden; + width: 2px; + height: 2px; } .control { From b11a15a71c514288cdf1db217396ba8f77a28603 Mon Sep 17 00:00:00 2001 From: Alessandro Vito <alessandro.vito84@gmail.com> Date: Tue, 5 Oct 2021 15:20:42 +0100 Subject: [PATCH 735/760] ADSDEV-924 Add ads as children of LiveBlogPost article --- .../x-live-blog-post/src/LiveBlogPost.jsx | 7 +++- .../src/LiveBlogWrapper.jsx | 12 +++++- .../x-live-blog-wrapper/storybook/index.jsx | 37 ++++++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 25c081b36..709a822a1 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -16,7 +16,8 @@ const LiveBlogPost = (props) => { standout = {}, articleUrl, showShareButtons = false, - byline + byline, + ad } = props const showBreakingNewsLabel = standout.breakingNews || isBreakingNews @@ -26,7 +27,8 @@ const LiveBlogPost = (props) => { className={`live-blog-post ${styles['live-blog-post']}`} data-trackable="live-post" id={`post-${id || postId}`} - data-x-component="live-blog-post"> + data-x-component="live-blog-post" + > <div className="live-blog-post__meta"> <Timestamp publishedTimestamp={publishedDate || publishedTimestamp} /> </div> @@ -38,6 +40,7 @@ const LiveBlogPost = (props) => { dangerouslySetInnerHTML={{ __html: bodyHTML || content }} /> {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} + {ad} </article> ) } diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 4dbab6f4a..45a86aef9 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -20,7 +20,14 @@ const withLiveBlogWrapperActions = withActions({ } }) -const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id, liveBlogWrapperElementRef }) => { +const BaseLiveBlogWrapper = ({ + posts = [], + ads = [], + articleUrl, + showShareButtons, + id, + liveBlogWrapperElementRef +}) => { posts.sort((a, b) => { const timestampA = a.publishedDate || a.publishedTimestamp const timestampB = b.publishedDate || b.publishedTimestamp @@ -37,12 +44,13 @@ const BaseLiveBlogWrapper = ({ posts = [], articleUrl, showShareButtons, id, liv return 0 }) - const postElements = posts.map((post) => ( + const postElements = posts.map((post, index) => ( <LiveBlogPost key={`live-blog-post-${post.id}`} {...post} articleUrl={articleUrl} showShareButtons={showShareButtons} + ad={ads[index]} /> )) diff --git a/components/x-live-blog-wrapper/storybook/index.jsx b/components/x-live-blog-wrapper/storybook/index.jsx index 250b8f2a5..f6e9ed7c0 100644 --- a/components/x-live-blog-wrapper/storybook/index.jsx +++ b/components/x-live-blog-wrapper/storybook/index.jsx @@ -2,6 +2,37 @@ import React from 'react' import { LiveBlogWrapper } from '../src/LiveBlogWrapper' import '../../x-live-blog-post/dist/LiveBlogPost.css' +const Ad = (props) => { + const { + slotName, + targeting, + defaultFormat = 'false', + small = 'false', + medium = 'false', + large = 'false', + extra = 'false', + alignment = 'center' + } = props + + const classes = `o-ads o-ads--${alignment} o-ads--transition` + + return ( + <div + data-o-ads-name={slotName} + data-o-ads-targeting={targeting} + data-o-ads-formats-default={defaultFormat} + data-o-ads-formats-small={small} + data-o-ads-formats-medium={medium} + data-o-ads-formats-large={large} + data-o-ads-formats-extra={extra} + data-o-ads-label="true" + aria-hidden="true" + tabIndex="-1" + className={classes} + /> + ) +} + const defaultProps = { message: 'Test', posts: [ @@ -32,7 +63,11 @@ const defaultProps = { articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true } - ] + ], + ads: { + 1: <Ad />, + 2: <Ad /> + } } export default { From bbd5085b9f62bdc7db11149cde3224e87181b393 Mon Sep 17 00:00:00 2001 From: Alessandro Vito <alessandro.vito84@gmail.com> Date: Tue, 5 Oct 2021 16:10:46 +0100 Subject: [PATCH 736/760] ADSDEV-924 ads prop is empty obj by default --- components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 45a86aef9..e76727ca3 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -22,7 +22,7 @@ const withLiveBlogWrapperActions = withActions({ const BaseLiveBlogWrapper = ({ posts = [], - ads = [], + ads = {}, articleUrl, showShareButtons, id, From c6a7a45dfb1e176101ff1f814fb482f93c2c6fb2 Mon Sep 17 00:00:00 2001 From: Alessandro Vito <alessandro.vito84@gmail.com> Date: Tue, 5 Oct 2021 17:36:36 +0100 Subject: [PATCH 737/760] ADSDEV-924 test that ad is inserted under the correct blog post --- .../src/__tests__/LiveBlogWrapper.test.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx index 9557f849c..6e876c475 100644 --- a/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx +++ b/components/x-live-blog-wrapper/src/__tests__/LiveBlogWrapper.test.jsx @@ -26,6 +26,14 @@ const post2 = { showShareButtons: true } +const ads = { + 1: ( + <div className="o-ads" data-o-ads-name="mid1"> + Ads + </div> + ) +} + describe('x-live-blog-wrapper', () => { it('has a displayName', () => { expect(LiveBlogWrapper.displayName).toContain('BaseLiveBlogWrapper') @@ -49,6 +57,15 @@ describe('x-live-blog-wrapper', () => { expect(articles.at(0).html()).toContain('Post 2 Title') expect(articles.at(1).html()).toContain('Post 1 Title') }) + + it('renders an ad slot element at the given position', () => { + const posts = [post1, post2] + const liveBlogWrapper = mount(<LiveBlogWrapper posts={posts} ads={ads} />) + + const articles = liveBlogWrapper.find('article') + expect(articles.at(0).html()).not.toContain('Ads') + expect(articles.at(1).html()).toContain('Ads') + }) }) describe('liveBlogWrapperActions', () => { From 7409b49a31edf329b8405f9955e611a67791621b Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Thu, 7 Oct 2021 10:07:18 +0100 Subject: [PATCH 738/760] updated o-normalise to ^3.0.0 --- components/x-privacy-manager/bower.json | 2 +- components/x-privacy-manager/src/components/radio-btn.scss | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/components/x-privacy-manager/bower.json b/components/x-privacy-manager/bower.json index 7dab1c91b..b12e6e0b5 100644 --- a/components/x-privacy-manager/bower.json +++ b/components/x-privacy-manager/bower.json @@ -9,7 +9,7 @@ "o-grid": "^5.0.0", "o-loading": "^4.0.0", "o-message": "^4.0.0", - "o-normalise": "^2.0.0", + "o-normalise": "^3.0.0", "o-spacing": "2.0.0", "o-typography": "6.0.0" } diff --git a/components/x-privacy-manager/src/components/radio-btn.scss b/components/x-privacy-manager/src/components/radio-btn.scss index d4609320a..5100130c1 100644 --- a/components/x-privacy-manager/src/components/radio-btn.scss +++ b/components/x-privacy-manager/src/components/radio-btn.scss @@ -8,8 +8,6 @@ $transitionDuration: 0.1s; .input { @include oNormaliseVisuallyHidden; - width: 2px; - height: 2px; } .control { From b0f9c1e480d4f87acaa73cd3fd4252655657f553 Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Thu, 7 Oct 2021 12:04:13 +0100 Subject: [PATCH 739/760] updated to ^2.0.8 --- components/x-privacy-manager/bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-privacy-manager/bower.json b/components/x-privacy-manager/bower.json index b12e6e0b5..46e4f79d3 100644 --- a/components/x-privacy-manager/bower.json +++ b/components/x-privacy-manager/bower.json @@ -9,7 +9,7 @@ "o-grid": "^5.0.0", "o-loading": "^4.0.0", "o-message": "^4.0.0", - "o-normalise": "^3.0.0", + "o-normalise": "^2.0.8", "o-spacing": "2.0.0", "o-typography": "6.0.0" } From 588453b241583263aaa0f78901c3f7bc3af7354b Mon Sep 17 00:00:00 2001 From: Oliver Turner <oliver.turner@codedsignal.co.uk> Date: Wed, 27 Oct 2021 14:35:17 +0100 Subject: [PATCH 740/760] Add outline-offset to ensure that contrast is sufficient --- components/x-privacy-manager/src/components/radio-btn.scss | 7 ++++--- package.json | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/components/x-privacy-manager/src/components/radio-btn.scss b/components/x-privacy-manager/src/components/radio-btn.scss index 5100130c1..537d0457a 100644 --- a/components/x-privacy-manager/src/components/radio-btn.scss +++ b/components/x-privacy-manager/src/components/radio-btn.scss @@ -42,11 +42,12 @@ $transitionDuration: 0.1s; background-color: oColorsByName('teal'); color: oColorsByName('white'); } - + // Since <input> itself is hidden, apply a familiar focus style to the visible <label> // As adjacent siblings we can reflect the <input>'s focus state here .input:focus + & { - outline: 2px solid oColorsByName('teal-100'); + outline: 2px solid oColorsByName('teal-40'); + outline-offset: 3px; background-color: oColorsByName('teal-40'); } @@ -67,7 +68,7 @@ $transitionDuration: 0.1s; font-size: 1.2rem; font-weight: 600; } - + & > span { display: block; margin-top: oSpacingByName(s1); diff --git a/package.json b/package.json index 8ae3e7a81..598afe8be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "name": "x-dash", "private": true, + "volta": { + "node": "12.22.7", + "npm": "7.24.2" + }, "scripts": { "clean": "git clean -fxdi", "build": "athloi run build --concurrency 3", From 4cbca527ee90aa9f2b97ed71572fb69e64b22216 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Mon, 1 Nov 2021 13:40:48 +0000 Subject: [PATCH 741/760] added back to top link to liveBlogPost --- .../x-live-blog-post/src/LiveBlogPost.jsx | 38 ++++++++++++++++++- .../x-live-blog-post/src/LiveBlogPost.scss | 23 +++++++++-- .../x-live-blog-post/src/ShareButtons.jsx | 15 +++++--- .../src/__tests__/LiveBlogPost.test.jsx | 22 +++++++++++ .../x-live-blog-post/storybook/index.jsx | 3 +- 5 files changed, 88 insertions(+), 13 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 709a822a1..dbdfde1a7 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { h } from '@financial-times/x-engine' import ShareButtons from './ShareButtons' import Timestamp from './Timestamp' @@ -17,11 +18,29 @@ const LiveBlogPost = (props) => { articleUrl, showShareButtons = false, byline, - ad + ad, + backToTop, + topRef } = props const showBreakingNewsLabel = standout.breakingNews || isBreakingNews + let backToTopProps = {} + + if (backToTop) { + backToTopProps = { + ...backToTopProps, + onClick: backToTop + } + } + + if (topRef) { + backToTopProps = { + ...backToTopProps, + href: topRef + } + } + return ( <article className={`live-blog-post ${styles['live-blog-post']}`} @@ -39,7 +58,22 @@ const LiveBlogPost = (props) => { className={`${styles['live-blog-post__body']} n-content-body article--body`} dangerouslySetInnerHTML={{ __html: bodyHTML || content }} /> - {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} + <div className={styles['live-blog-post__bottom-controls']}> + {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} + {(Boolean(backToTop) || Boolean(topRef)) && ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events + <a + {...backToTopProps} + aria-labelledby="Back to top link" + {...backToTopProps} + className={styles['back-to-top']} + onClick={backToTop} + > + Back to top + </a> + )} + </div> + {ad} </article> ) diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index ca983ef58..803bf986e 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -54,10 +54,6 @@ padding-top: oSpacingByName('s1'); } -.live-blog-post__share-buttons { - margin-top: oSpacingByName('s6'); -} - .live-blog-post__breaking-news { @include oTypographySans($scale: -2); color: oColorsByName('crimson'); @@ -74,3 +70,22 @@ border-radius: 50%; background-color: oColorsByName('crimson'); } + +.live-blog-post__bottom-controls { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + margin-top: oSpacingByName('s6'); +} + +.live-blog-post__bottom-controls .back-to-top { + @include oTypographySans($scale: 1); + color: oColorsByName('teal'); + text-decoration: underline; + margin-left: auto; +} + +.live-blog-post__bottom-controls .back-to-top:hover { + cursor: pointer; +} diff --git a/components/x-live-blog-post/src/ShareButtons.jsx b/components/x-live-blog-post/src/ShareButtons.jsx index 0ed3ad888..9c6f3abce 100644 --- a/components/x-live-blog-post/src/ShareButtons.jsx +++ b/components/x-live-blog-post/src/ShareButtons.jsx @@ -1,5 +1,4 @@ import { h } from '@financial-times/x-engine' -import styles from './LiveBlogPost.scss' export default ({ postId, articleUrl, title }) => { const shareUrl = articleUrl ? new URL(articleUrl) : null @@ -18,18 +17,20 @@ export default ({ postId, articleUrl, title }) => { )}&title=${encodeURIComponent(title)}&source=Financial+Times` return ( - <div className={styles['live-blog-post__share-buttons']}> + <div> <div data-o-component="o-share" data-o-share-location={`live-blog-post-${postId}`} - className="o-share o-share--small"> + className="o-share o-share--small" + > <ul data-toolbar="share"> <li className="o-share__action" data-share="twitter"> <a className="o-share__icon o-share__icon--twitter" rel="noopener" href={twitterUrl} - data-trackable="twitter"> + data-trackable="twitter" + > <span className="o-share__text" aria-label={`Share ${title} on Twitter`}> Share on Twitter (opens new window) </span> @@ -40,7 +41,8 @@ export default ({ postId, articleUrl, title }) => { className="o-share__icon o-share__icon--facebook" rel="noopener" href={facebookUrl} - data-trackable="facebook"> + data-trackable="facebook" + > <span className="o-share__text" aria-label={`Share ${title} on Facebook`}> Share on Facebook (opens new window) </span> @@ -51,7 +53,8 @@ export default ({ postId, articleUrl, title }) => { className="o-share__icon o-share__icon--linkedin" rel="noopener" href={linkedInUrl} - data-trackable="linkedin"> + data-trackable="linkedin" + > <span className="o-share__text" aria-label={`Share ${title} on LinkedIn`}> Share on LinkedIn (opens new window) </span> diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index b038c90fe..f56e998f4 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -47,6 +47,19 @@ const regularPostSpark = { showShareButtons: true } +const backToTopPostSpark = { + id: '12345', + title: 'Test title', + byline: 'Test author', + bodyHTML: '<p><i>Test body</i></p>', + publishedDate: new Date().toISOString(), + isBreakingNews: false, + articleUrl: 'Https://www.ft.com', + showShareButtons: true, + backToTop: () => {}, + topRef: '#top' +} + describe('x-live-blog-post', () => { describe('Spark cms', () => { describe('title property exists', () => { @@ -107,6 +120,15 @@ describe('x-live-blog-post', () => { expect(liveBlogPost.html()).toContain('<p><i>Test body</i></p>') }) + + describe('Back to top post', () => { + it('renders back to top link', () => { + const liveBlogPost = mount(<LiveBlogPost {...backToTopPostSpark} />) + + expect(liveBlogPost.html()).toContain('Back to top') + expect(liveBlogPost.html()).toContain('</a>') + }) + }) }) describe('Wordpress cms', () => { diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index f17f6e759..7337fdb35 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -34,5 +34,6 @@ ContentBody.args = { id: '12345', publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', - showShareButtons: true + showShareButtons: true, + backToTop: () => {} } From abd78e28ccdef53b14564861e73a26c38eac577e Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Mon, 1 Nov 2021 13:47:26 +0000 Subject: [PATCH 742/760] removed onClick attribute from back to top link --- components/x-live-blog-post/src/LiveBlogPost.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index dbdfde1a7..a243a254b 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -64,10 +64,9 @@ const LiveBlogPost = (props) => { // eslint-disable-next-line jsx-a11y/click-events-have-key-events <a {...backToTopProps} - aria-labelledby="Back to top link" + aria-labelledby="Back to top" {...backToTopProps} className={styles['back-to-top']} - onClick={backToTop} > Back to top </a> From 2cab00173efd3cac10f470cc59cf537d78159e3b Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Mon, 1 Nov 2021 13:57:05 +0000 Subject: [PATCH 743/760] onClick attribute from back to top link --- components/x-live-blog-post/src/LiveBlogPost.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index a243a254b..4983fadd2 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -27,17 +27,17 @@ const LiveBlogPost = (props) => { let backToTopProps = {} - if (backToTop) { + if (topRef) { backToTopProps = { ...backToTopProps, - onClick: backToTop + href: topRef } } - if (topRef) { + if (backToTop) { backToTopProps = { ...backToTopProps, - href: topRef + onClick: backToTop } } From 550e0fa95bac9271f5f6827e9d1384c06d9e29f1 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Mon, 1 Nov 2021 14:13:09 +0000 Subject: [PATCH 744/760] updated x-live-blog-post readme --- components/x-live-blog-post/readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index 725846c76..af78ad65d 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -52,3 +52,6 @@ Feature | Type | Notes `publishedTimestamp`| String | Deprecated - ISO timestamp of publish date `articleUrl` | String | Url of the main article that includes this post `showShareButtons` | Bool | default: `false` - Shows social media share buttons when `true` +`topRef` | String | Shows the back to top link at the bottom of posts and navigates to the hashed section of the page. No defaults, example `#top` +`backToTop` | Function | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function. No defaults. Only pass in `topRef` or `backToTop` props to the component. Please call event.preventDefault() at the top level of this function. + From 9e86bbcf529fc4cf4d7e92856a3b3b32e75c4dad Mon Sep 17 00:00:00 2001 From: gitGremlin <mo.shawwa@ft.com> Date: Mon, 1 Nov 2021 19:23:42 +0000 Subject: [PATCH 745/760] included outline-offset and changed colours for the save button --- components/x-privacy-manager/src/privacy-manager.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/x-privacy-manager/src/privacy-manager.scss b/components/x-privacy-manager/src/privacy-manager.scss index 662951d7b..ba9325a28 100644 --- a/components/x-privacy-manager/src/privacy-manager.scss +++ b/components/x-privacy-manager/src/privacy-manager.scss @@ -74,6 +74,8 @@ padding: 0 oSpacingByName(m12); &:focus-visible { - outline: 2px solid oColorsByName('teal-100'); + outline: 2px solid oColorsByName('teal-40'); + outline-offset: 3px; + background-color: oColorsByName('teal-40'); } } From 6e33abc39e2ad0f35bc7d315e278c157430df2e0 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 2 Nov 2021 10:40:12 +0000 Subject: [PATCH 746/760] renamed css classes inline with BEM --- components/x-live-blog-post/src/LiveBlogPost.jsx | 4 ++-- components/x-live-blog-post/src/LiveBlogPost.scss | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 4983fadd2..feacb09e5 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -58,7 +58,7 @@ const LiveBlogPost = (props) => { className={`${styles['live-blog-post__body']} n-content-body article--body`} dangerouslySetInnerHTML={{ __html: bodyHTML || content }} /> - <div className={styles['live-blog-post__bottom-controls']}> + <div className={styles['live-blog-post__controls']}> {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} {(Boolean(backToTop) || Boolean(topRef)) && ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events @@ -66,7 +66,7 @@ const LiveBlogPost = (props) => { {...backToTopProps} aria-labelledby="Back to top" {...backToTopProps} - className={styles['back-to-top']} + className={styles['live-blog-post-controls__back-to-top']} > Back to top </a> diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 803bf986e..4cefa8694 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -71,7 +71,7 @@ background-color: oColorsByName('crimson'); } -.live-blog-post__bottom-controls { +.live-blog-post__controls { display: flex; justify-content: space-between; align-items: center; @@ -79,13 +79,13 @@ margin-top: oSpacingByName('s6'); } -.live-blog-post__bottom-controls .back-to-top { +.live-blog-post__controls .live-blog-post-controls__back-to-top { @include oTypographySans($scale: 1); color: oColorsByName('teal'); text-decoration: underline; margin-left: auto; } -.live-blog-post__bottom-controls .back-to-top:hover { +.live-blog-post__bottom-controls .live-blog-post-controls__back-to-top:hover { cursor: pointer; } From 24a0d01f2875aaab616820d26ee5dae718cf83e3 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 2 Nov 2021 10:57:57 +0000 Subject: [PATCH 747/760] renamed backToTop props to provide more context --- components/x-live-blog-post/readme.md | 4 ++-- .../x-live-blog-post/src/LiveBlogPost.jsx | 17 ++++++++++------- .../x-live-blog-post/src/LiveBlogPost.scss | 2 +- components/x-live-blog-post/storybook/index.jsx | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index af78ad65d..146724d35 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -52,6 +52,6 @@ Feature | Type | Notes `publishedTimestamp`| String | Deprecated - ISO timestamp of publish date `articleUrl` | String | Url of the main article that includes this post `showShareButtons` | Bool | default: `false` - Shows social media share buttons when `true` -`topRef` | String | Shows the back to top link at the bottom of posts and navigates to the hashed section of the page. No defaults, example `#top` -`backToTop` | Function | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function. No defaults. Only pass in `topRef` or `backToTop` props to the component. Please call event.preventDefault() at the top level of this function. +`backToTopRef` | String | Shows the back to top link at the bottom of posts and navigates to the hashed section of the page. No defaults, example `#top` +`backToTopFunction` | Function | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function. No defaults. Only pass in `backToTopRef` or `backToTopFunction` props to the component. Please call event.preventDefault() at the top level of this function. diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index feacb09e5..9863778f0 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -19,25 +19,28 @@ const LiveBlogPost = (props) => { showShareButtons = false, byline, ad, - backToTop, - topRef + backToTopFunction, + backToTopRef } = props const showBreakingNewsLabel = standout.breakingNews || isBreakingNews let backToTopProps = {} - if (topRef) { + if (backToTopRef) { + const processTopRef = (ref) => { + return typeof ref === 'string' && ref.includes('#') ? ref : `#${ref}` + } backToTopProps = { ...backToTopProps, - href: topRef + href: processTopRef(backToTopRef) } } - if (backToTop) { + if (backToTopFunction) { backToTopProps = { ...backToTopProps, - onClick: backToTop + onClick: backToTopFunction } } @@ -60,7 +63,7 @@ const LiveBlogPost = (props) => { /> <div className={styles['live-blog-post__controls']}> {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} - {(Boolean(backToTop) || Boolean(topRef)) && ( + {(Boolean(backToTopFunction) || Boolean(backToTopRef)) && ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events <a {...backToTopProps} diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 4cefa8694..a1a186e0d 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -86,6 +86,6 @@ margin-left: auto; } -.live-blog-post__bottom-controls .live-blog-post-controls__back-to-top:hover { +.live-blog-post__controls .live-blog-post-controls__back-to-top:hover { cursor: pointer; } diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index 7337fdb35..2aad2a215 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -35,5 +35,5 @@ ContentBody.args = { publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true, - backToTop: () => {} + backToTopFunction: () => {} } From 884196a0bfa5086f3474733ae768a06efae8fd13 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 2 Nov 2021 11:05:12 +0000 Subject: [PATCH 748/760] removed duplicate destructured props --- components/x-live-blog-post/src/LiveBlogPost.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 9863778f0..887e79f41 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -68,7 +68,6 @@ const LiveBlogPost = (props) => { <a {...backToTopProps} aria-labelledby="Back to top" - {...backToTopProps} className={styles['live-blog-post-controls__back-to-top']} > Back to top From bfbccce70a982186915e6e1403d67331fa15eaf2 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 2 Nov 2021 11:17:19 +0000 Subject: [PATCH 749/760] updated liveBlogPost test --- .../x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index f56e998f4..4ba6218a4 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -56,8 +56,8 @@ const backToTopPostSpark = { isBreakingNews: false, articleUrl: 'Https://www.ft.com', showShareButtons: true, - backToTop: () => {}, - topRef: '#top' + backToTopFunction: () => {}, + backToTopRef: '#top' } describe('x-live-blog-post', () => { From df89b1b3a4d9f8617429efbb6867a3c81f6d406e Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 2 Nov 2021 14:13:04 +0000 Subject: [PATCH 750/760] combined backToTopRef and backToTopFunction into a single prop --- components/x-live-blog-post/readme.md | 3 +-- .../x-live-blog-post/src/LiveBlogPost.jsx | 25 +++++++---------- .../src/__tests__/LiveBlogPost.test.jsx | 3 +-- .../x-live-blog-post/storybook/index.jsx | 27 ++++++++++++++++++- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index 146724d35..08be3b8c5 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -52,6 +52,5 @@ Feature | Type | Notes `publishedTimestamp`| String | Deprecated - ISO timestamp of publish date `articleUrl` | String | Url of the main article that includes this post `showShareButtons` | Bool | default: `false` - Shows social media share buttons when `true` -`backToTopRef` | String | Shows the back to top link at the bottom of posts and navigates to the hashed section of the page. No defaults, example `#top` -`backToTopFunction` | Function | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function. No defaults. Only pass in `backToTopRef` or `backToTopFunction` props to the component. Please call event.preventDefault() at the top level of this function. +`backToTop` | Function | String | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function or a hashed link. Only pass in. Please call event.preventDefault() at the top level if this prop is a function. If prop is a string, add the string as the `id` to the element that represents the top. diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 887e79f41..9cc071c96 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -19,28 +19,23 @@ const LiveBlogPost = (props) => { showShareButtons = false, byline, ad, - backToTopFunction, - backToTopRef + backToTop } = props const showBreakingNewsLabel = standout.breakingNews || isBreakingNews let backToTopProps = {} - if (backToTopRef) { - const processTopRef = (ref) => { - return typeof ref === 'string' && ref.includes('#') ? ref : `#${ref}` + if (backToTop) { + if (typeof backToTop === 'string') { + const processTopRef = (ref) => { + return ref.includes('#') ? ref : `#${ref}` + } + backToTopProps.href = processTopRef(backToTop) } - backToTopProps = { - ...backToTopProps, - href: processTopRef(backToTopRef) - } - } - if (backToTopFunction) { - backToTopProps = { - ...backToTopProps, - onClick: backToTopFunction + if (typeof backToTop === 'function') { + backToTopProps.onClick = backToTop } } @@ -63,7 +58,7 @@ const LiveBlogPost = (props) => { /> <div className={styles['live-blog-post__controls']}> {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} - {(Boolean(backToTopFunction) || Boolean(backToTopRef)) && ( + {Boolean(backToTop) && ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events <a {...backToTopProps} diff --git a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx index 4ba6218a4..be108307a 100644 --- a/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx +++ b/components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx @@ -56,8 +56,7 @@ const backToTopPostSpark = { isBreakingNews: false, articleUrl: 'Https://www.ft.com', showShareButtons: true, - backToTopFunction: () => {}, - backToTopRef: '#top' + backToTop: () => {} } describe('x-live-blog-post', () => { diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index 2aad2a215..af253724e 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -35,5 +35,30 @@ ContentBody.args = { publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true, - backToTopFunction: () => {} + backToTop: () => {} +} + +export const ContentBodyWithBackToTopString = (args) => { + return ( + <div className="story-container"> + {dependencies && <BuildService dependencies={dependencies} />} + <LiveBlogPost {...args} /> + </div> + ) +} + +ContentBodyWithBackToTopString.args = { + title: 'Turkey’s virus deaths may be 25% higher than official figure', + byline: 'George Russell', + isBreakingNews: false, + standout: { + breakingNews: false + }, + bodyHTML: + '<p>Turkey’s death toll from coronavirus could be as much as 25 per cent higher than the government’s official tally, adding the country of 83m people to the raft of nations that have struggled to accurately capture the impact of the pandemic.</p>\n<p>Ankara has previously rejected suggestions that municipal data from Istanbul, the epicentre of the country’s Covid-19 outbreak, showed that there were more deaths from the disease than reported.</p>\n<p>But an analysis of individual death records by the Financial Times raises questions about the Turkish government’s explanation for a spike in all-cause mortality in the city of almost 16m people.</p>\n<p><a href="https://www.ft.com/content/80bb222c-b6eb-40ea-8014-563cbe9e0117" target="_blank">Read the article here</a></p>\n<p><img class="picture" src="http://blogs.ft.com/the-world/files/2020/05/istanbul_excess_morts_l.jpg"></p>', + id: '12345', + publishedDate: '2020-05-13T18:52:28.000Z', + articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', + showShareButtons: true, + backToTop: '#Top' } From 3cacdc77022593ec3e8c592399f72f28025b2bf1 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 2 Nov 2021 15:22:30 +0000 Subject: [PATCH 751/760] updated readme for x-live-blog-psot --- components/x-live-blog-post/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index 08be3b8c5..be2001f28 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -52,5 +52,5 @@ Feature | Type | Notes `publishedTimestamp`| String | Deprecated - ISO timestamp of publish date `articleUrl` | String | Url of the main article that includes this post `showShareButtons` | Bool | default: `false` - Shows social media share buttons when `true` -`backToTop` | Function | String | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function or a hashed link. Only pass in. Please call event.preventDefault() at the top level if this prop is a function. If prop is a string, add the string as the `id` to the element that represents the top. +`backToTop` | Function | String | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function or a hashed href. Please call event.preventDefault() at the top level if this prop is a function. If prop is a string, add the string as the `id` to the element that represents the top. From d3e8c03321aac17105616824017aa4c276a1255d Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <5596564+noelenwenede@users.noreply.github.com> Date: Wed, 3 Nov 2021 14:02:25 +0000 Subject: [PATCH 752/760] Update components/x-live-blog-post/readme.md Co-authored-by: Glynn Phillips <524573+GlynnPhillips@users.noreply.github.com> --- components/x-live-blog-post/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/x-live-blog-post/readme.md b/components/x-live-blog-post/readme.md index be2001f28..b3d189401 100644 --- a/components/x-live-blog-post/readme.md +++ b/components/x-live-blog-post/readme.md @@ -52,5 +52,5 @@ Feature | Type | Notes `publishedTimestamp`| String | Deprecated - ISO timestamp of publish date `articleUrl` | String | Url of the main article that includes this post `showShareButtons` | Bool | default: `false` - Shows social media share buttons when `true` -`backToTop` | Function | String | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function or a hashed href. Please call event.preventDefault() at the top level if this prop is a function. If prop is a string, add the string as the `id` to the element that represents the top. +`backToTop` | String | Function | Shows the back to top link at the bottom of posts and manages navigating to `selected top` with a javascript function or a hashed href (string). If this prop is a string it will rely on standard browser behaviour to navigate to the element `id` provided that represents the top. If this prop is a function then that function should control the experience of navigating/scrolling to the top position. When using a function please call event.preventDefault() at the top level. From 00d53f5f33c2cb62015379f8f6408f6e7c4d3985 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Thu, 4 Nov 2021 14:47:45 +0000 Subject: [PATCH 753/760] used button as BackToTop component when a function is passed in as prop and a tag when a hash link is passed --- .../x-live-blog-post/src/LiveBlogPost.jsx | 33 +++++++++++-------- .../x-live-blog-post/src/LiveBlogPost.scss | 10 ++++-- .../x-live-blog-post/storybook/index.jsx | 8 ++--- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 9cc071c96..6700e3ae6 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -24,18 +24,34 @@ const LiveBlogPost = (props) => { const showBreakingNewsLabel = standout.breakingNews || isBreakingNews - let backToTopProps = {} + let BackToTopComponent if (backToTop) { if (typeof backToTop === 'string') { const processTopRef = (ref) => { return ref.includes('#') ? ref : `#${ref}` } - backToTopProps.href = processTopRef(backToTop) + BackToTopComponent = ( + <a + href={processTopRef(backToTop)} + aria-labelledby="Back to top" + className={styles['live-blog-post-controls__back-to-top-link']} + > + Back to top + </a> + ) } if (typeof backToTop === 'function') { - backToTopProps.onClick = backToTop + BackToTopComponent = ( + <button + onClick={backToTop} + aria-labelledby="Back to top" + className={styles['live-blog-post-controls__back-to-top-button']} + > + Back to top + </button> + ) } } @@ -58,16 +74,7 @@ const LiveBlogPost = (props) => { /> <div className={styles['live-blog-post__controls']}> {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} - {Boolean(backToTop) && ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events - <a - {...backToTopProps} - aria-labelledby="Back to top" - className={styles['live-blog-post-controls__back-to-top']} - > - Back to top - </a> - )} + {Boolean(BackToTopComponent) && <>{BackToTopComponent}</>} </div> {ad} diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index a1a186e0d..2e329d21f 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -79,13 +79,19 @@ margin-top: oSpacingByName('s6'); } -.live-blog-post__controls .live-blog-post-controls__back-to-top { +.live-blog-post__controls .live-blog-post-controls__back-to-top-link, +.live-blog-post__controls .live-blog-post-controls__back-to-top-button { @include oTypographySans($scale: 1); color: oColorsByName('teal'); text-decoration: underline; margin-left: auto; } -.live-blog-post__controls .live-blog-post-controls__back-to-top:hover { +.live-blog-post__controls .live-blog-post-controls__back-to-top-button { + background: unset; + border: unset; +} + +.live-blog-post__controls .live-blog-post-controls__back-to-top-button:hover { cursor: pointer; } diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index af253724e..b8528a179 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -35,10 +35,10 @@ ContentBody.args = { publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true, - backToTop: () => {} + backToTop: '#Top' } -export const ContentBodyWithBackToTopString = (args) => { +export const ContentBodyWithBackToTopButton = (args) => { return ( <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} @@ -47,7 +47,7 @@ export const ContentBodyWithBackToTopString = (args) => { ) } -ContentBodyWithBackToTopString.args = { +ContentBodyWithBackToTopButton.args = { title: 'Turkey’s virus deaths may be 25% higher than official figure', byline: 'George Russell', isBreakingNews: false, @@ -60,5 +60,5 @@ ContentBodyWithBackToTopString.args = { publishedDate: '2020-05-13T18:52:28.000Z', articleUrl: 'https://www.ft.com/content/2b665ec7-a88f-3998-8f39-5371f9c791ed', showShareButtons: true, - backToTop: '#Top' + backToTop: () => {} } From df4338c12466b8845b4e48ffe90219e8b580dff7 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Thu, 4 Nov 2021 15:24:26 +0000 Subject: [PATCH 754/760] added Fragment to host BactToTop component --- components/x-live-blog-post/src/LiveBlogPost.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 6700e3ae6..0f217f2ef 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -1,5 +1,5 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ -import { h } from '@financial-times/x-engine' +import { h, Fragment } from '@financial-times/x-engine' import ShareButtons from './ShareButtons' import Timestamp from './Timestamp' import styles from './LiveBlogPost.scss' @@ -74,7 +74,7 @@ const LiveBlogPost = (props) => { /> <div className={styles['live-blog-post__controls']}> {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} - {Boolean(BackToTopComponent) && <>{BackToTopComponent}</>} + {Boolean(BackToTopComponent) && <Fragment>{BackToTopComponent}</Fragment>} </div> {ad} From 90b012f6284f8a10539dd8cd89ea2567b801b6db Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Mon, 8 Nov 2021 13:23:05 +0000 Subject: [PATCH 755/760] added a first child selector to only show back-to-top link on all Posts except the first post --- components/x-live-blog-post/src/LiveBlogPost.jsx | 4 ++-- components/x-live-blog-post/src/LiveBlogPost.scss | 5 +++++ components/x-live-blog-post/storybook/index.jsx | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 0f217f2ef..1ddb53f1b 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -35,7 +35,7 @@ const LiveBlogPost = (props) => { <a href={processTopRef(backToTop)} aria-labelledby="Back to top" - className={styles['live-blog-post-controls__back-to-top-link']} + className={`live-blog-post-controls__back-to-top-link ${styles['live-blog-post-controls__back-to-top-link']}`} > Back to top </a> @@ -47,7 +47,7 @@ const LiveBlogPost = (props) => { <button onClick={backToTop} aria-labelledby="Back to top" - className={styles['live-blog-post-controls__back-to-top-button']} + className={`live-blog-post-controls__back-to-top-button ${styles['live-blog-post-controls__back-to-top-button']}`} > Back to top </button> diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 2e329d21f..6440af8ea 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -95,3 +95,8 @@ .live-blog-post__controls .live-blog-post-controls__back-to-top-button:hover { cursor: pointer; } + +.live-blog-post:first-child .live-blog-post-controls__back-to-top-link, +.live-blog-post:first-child .live-blog-post-controls__back-to-top-button { + display: none; +} diff --git a/components/x-live-blog-post/storybook/index.jsx b/components/x-live-blog-post/storybook/index.jsx index b8528a179..25cc0f064 100644 --- a/components/x-live-blog-post/storybook/index.jsx +++ b/components/x-live-blog-post/storybook/index.jsx @@ -18,6 +18,7 @@ export const ContentBody = (args) => { <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} <LiveBlogPost {...args} /> + <LiveBlogPost {...args} /> </div> ) } @@ -43,6 +44,7 @@ export const ContentBodyWithBackToTopButton = (args) => { <div className="story-container"> {dependencies && <BuildService dependencies={dependencies} />} <LiveBlogPost {...args} /> + <LiveBlogPost {...args} /> </div> ) } From 881223402951e0849d585f483ab9c80a8365d437 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 9 Nov 2021 11:20:42 +0000 Subject: [PATCH 756/760] added comments to explain use of extra class name --- components/x-live-blog-post/src/LiveBlogPost.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 1ddb53f1b..fae64c788 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -32,6 +32,12 @@ const LiveBlogPost = (props) => { return ref.includes('#') ? ref : `#${ref}` } BackToTopComponent = ( + /** + * (Ref: LBPC101) The class name `live-blog-post-controls__back-to-top-link` has been added to help consumers + * of this component select and override css behaviours if it is required. Due to modularization. + * the generated classnames will be differen on different releases so this addition provides a steady + * selector for overrides + */ <a href={processTopRef(backToTop)} aria-labelledby="Back to top" @@ -44,6 +50,9 @@ const LiveBlogPost = (props) => { if (typeof backToTop === 'function') { BackToTopComponent = ( + /** + * (Ref: LBPC101) Override class name `live-blog-post-controls__back-to-top-button` + */ <button onClick={backToTop} aria-labelledby="Back to top" @@ -56,6 +65,9 @@ const LiveBlogPost = (props) => { } return ( + /** + * (Ref: LBPC101) Override class name `live-blog-post` + */ <article className={`live-blog-post ${styles['live-blog-post']}`} data-trackable="live-post" From a1afa66902713eeb12c69f5e83e4599cae46b5ef Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 9 Nov 2021 13:53:35 +0000 Subject: [PATCH 757/760] retrigger checks From 7075f90511d123aeef07f95261a1719537878823 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Tue, 9 Nov 2021 14:19:12 +0000 Subject: [PATCH 758/760] removed class name selector from back-to-top component --- components/x-live-blog-post/src/LiveBlogPost.jsx | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index fae64c788..0f217f2ef 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -32,16 +32,10 @@ const LiveBlogPost = (props) => { return ref.includes('#') ? ref : `#${ref}` } BackToTopComponent = ( - /** - * (Ref: LBPC101) The class name `live-blog-post-controls__back-to-top-link` has been added to help consumers - * of this component select and override css behaviours if it is required. Due to modularization. - * the generated classnames will be differen on different releases so this addition provides a steady - * selector for overrides - */ <a href={processTopRef(backToTop)} aria-labelledby="Back to top" - className={`live-blog-post-controls__back-to-top-link ${styles['live-blog-post-controls__back-to-top-link']}`} + className={styles['live-blog-post-controls__back-to-top-link']} > Back to top </a> @@ -50,13 +44,10 @@ const LiveBlogPost = (props) => { if (typeof backToTop === 'function') { BackToTopComponent = ( - /** - * (Ref: LBPC101) Override class name `live-blog-post-controls__back-to-top-button` - */ <button onClick={backToTop} aria-labelledby="Back to top" - className={`live-blog-post-controls__back-to-top-button ${styles['live-blog-post-controls__back-to-top-button']}`} + className={styles['live-blog-post-controls__back-to-top-button']} > Back to top </button> @@ -65,9 +56,6 @@ const LiveBlogPost = (props) => { } return ( - /** - * (Ref: LBPC101) Override class name `live-blog-post` - */ <article className={`live-blog-post ${styles['live-blog-post']}`} data-trackable="live-post" From 7c10eb03bcee5b9812c2d483e12487f6e2b388dd Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Wed, 10 Nov 2021 14:17:59 +0000 Subject: [PATCH 759/760] removed fragment from live-blog-post component --- .../x-live-blog-post/src/LiveBlogPost.jsx | 11 ++++++----- .../x-live-blog-post/src/LiveBlogPost.scss | 17 ++++++++++------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index 0f217f2ef..e15586f36 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -1,5 +1,4 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -import { h, Fragment } from '@financial-times/x-engine' +import { h } from '@financial-times/x-engine' import ShareButtons from './ShareButtons' import Timestamp from './Timestamp' import styles from './LiveBlogPost.scss' @@ -35,7 +34,7 @@ const LiveBlogPost = (props) => { <a href={processTopRef(backToTop)} aria-labelledby="Back to top" - className={styles['live-blog-post-controls__back-to-top-link']} + className={styles['live-blog-post-controls__right__back-to-top-link']} > Back to top </a> @@ -47,7 +46,7 @@ const LiveBlogPost = (props) => { <button onClick={backToTop} aria-labelledby="Back to top" - className={styles['live-blog-post-controls__back-to-top-button']} + className={styles['live-blog-post-controls__right__back-to-top-button']} > Back to top </button> @@ -74,7 +73,9 @@ const LiveBlogPost = (props) => { /> <div className={styles['live-blog-post__controls']}> {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} - {Boolean(BackToTopComponent) && <Fragment>{BackToTopComponent}</Fragment>} + {Boolean(BackToTopComponent) && ( + <div className={styles['live-blog-post__controls__right']}>{BackToTopComponent}</div> + )} </div> {ad} diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index 6440af8ea..b68011a3a 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -79,24 +79,27 @@ margin-top: oSpacingByName('s6'); } -.live-blog-post__controls .live-blog-post-controls__back-to-top-link, -.live-blog-post__controls .live-blog-post-controls__back-to-top-button { +.live-blog-post__controls__right { + margin-left: auto; +} + +.live-blog-post__controls__right .live-blog-post-controls__right__back-to-top-link, +.live-blog-post__controls__right .live-blog-post-controls__right__back-to-top-button { @include oTypographySans($scale: 1); color: oColorsByName('teal'); text-decoration: underline; - margin-left: auto; } -.live-blog-post__controls .live-blog-post-controls__back-to-top-button { +.live-blog-post__controls__right .live-blog-post-controls__right__back-to-top-button { background: unset; border: unset; } -.live-blog-post__controls .live-blog-post-controls__back-to-top-button:hover { +.live-blog-post__controls__right .live-blog-post-controls__right__back-to-top-button:hover { cursor: pointer; } -.live-blog-post:first-child .live-blog-post-controls__back-to-top-link, -.live-blog-post:first-child .live-blog-post-controls__back-to-top-button { +.live-blog-post:first-child .live-blog-post-controls__right__back-to-top-link, +.live-blog-post:first-child .live-blog-post-controls__right__back-to-top-button { display: none; } From b1570c0bd54aa0e04ffc86746c6692baf03228b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Enwenede <emmanuel@blueeel.co.uk> Date: Thu, 11 Nov 2021 16:05:44 +0000 Subject: [PATCH 760/760] refactored the x-live-blog-component. Also reintroduced the use of Fragment within the component which was removed before. The refactor was done mainly because the CSS wasn't BEM compliant --- .../x-live-blog-post/src/LiveBlogPost.jsx | 78 +++++++++++-------- .../x-live-blog-post/src/LiveBlogPost.scss | 19 ++--- 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/components/x-live-blog-post/src/LiveBlogPost.jsx b/components/x-live-blog-post/src/LiveBlogPost.jsx index e15586f36..7bc2a8c21 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.jsx +++ b/components/x-live-blog-post/src/LiveBlogPost.jsx @@ -1,8 +1,49 @@ -import { h } from '@financial-times/x-engine' +import { h, Fragment } from '@financial-times/x-engine' import ShareButtons from './ShareButtons' import Timestamp from './Timestamp' import styles from './LiveBlogPost.scss' +/** + * Triggers a page scroll depending on what the type of `backToTopProp` is. + * A function will be called onClick. + * A string with be transformed to a hashed href. e.g backToTopProp="top" becomes "#top" + * + * @param {(function | string)} backToTopProp + * @returns + */ +function generateBackToTopComponent(backToTopProp) { + if (!backToTopProp) { + return + } + + if (typeof backToTopProp === 'string') { + const processTopRef = (ref) => { + return ref.includes('#') ? ref : `#${ref}` + } + return ( + <a + href={processTopRef(backToTopProp)} + aria-labelledby="Back to top" + className={styles['live-blog-post-controls__back-to-top-link']} + > + Back to top + </a> + ) + } + + if (typeof backToTopProp === 'function') { + return ( + <button + onClick={backToTopProp} + aria-labelledby="Back to top" + className={styles['live-blog-post-controls__back-to-top-button']} + > + Back to top + </button> + ) + } +} + const LiveBlogPost = (props) => { const { id, @@ -23,36 +64,7 @@ const LiveBlogPost = (props) => { const showBreakingNewsLabel = standout.breakingNews || isBreakingNews - let BackToTopComponent - - if (backToTop) { - if (typeof backToTop === 'string') { - const processTopRef = (ref) => { - return ref.includes('#') ? ref : `#${ref}` - } - BackToTopComponent = ( - <a - href={processTopRef(backToTop)} - aria-labelledby="Back to top" - className={styles['live-blog-post-controls__right__back-to-top-link']} - > - Back to top - </a> - ) - } - - if (typeof backToTop === 'function') { - BackToTopComponent = ( - <button - onClick={backToTop} - aria-labelledby="Back to top" - className={styles['live-blog-post-controls__right__back-to-top-button']} - > - Back to top - </button> - ) - } - } + const BackToTopComponent = generateBackToTopComponent(backToTop) return ( <article @@ -73,9 +85,7 @@ const LiveBlogPost = (props) => { /> <div className={styles['live-blog-post__controls']}> {showShareButtons && <ShareButtons postId={id || postId} articleUrl={articleUrl} title={title} />} - {Boolean(BackToTopComponent) && ( - <div className={styles['live-blog-post__controls__right']}>{BackToTopComponent}</div> - )} + {Boolean(BackToTopComponent) && <Fragment>{BackToTopComponent}</Fragment>} </div> {ad} diff --git a/components/x-live-blog-post/src/LiveBlogPost.scss b/components/x-live-blog-post/src/LiveBlogPost.scss index b68011a3a..62731fec6 100644 --- a/components/x-live-blog-post/src/LiveBlogPost.scss +++ b/components/x-live-blog-post/src/LiveBlogPost.scss @@ -79,27 +79,24 @@ margin-top: oSpacingByName('s6'); } -.live-blog-post__controls__right { - margin-left: auto; -} - -.live-blog-post__controls__right .live-blog-post-controls__right__back-to-top-link, -.live-blog-post__controls__right .live-blog-post-controls__right__back-to-top-button { +.live-blog-post__controls .live-blog-post-controls__back-to-top-link, +.live-blog-post__controls .live-blog-post-controls__back-to-top-button { @include oTypographySans($scale: 1); color: oColorsByName('teal'); text-decoration: underline; + margin-left: auto; } -.live-blog-post__controls__right .live-blog-post-controls__right__back-to-top-button { +.live-blog-post__controls .live-blog-post-controls__back-to-top-button { background: unset; border: unset; } -.live-blog-post__controls__right .live-blog-post-controls__right__back-to-top-button:hover { +.live-blog-post__controls .live-blog-post-controls__back-to-top-button:hover { cursor: pointer; } -.live-blog-post:first-child .live-blog-post-controls__right__back-to-top-link, -.live-blog-post:first-child .live-blog-post-controls__right__back-to-top-button { +.live-blog-post:first-child .live-blog-post-controls__back-to-top-link, +.live-blog-post:first-child .live-blog-post-controls__back-to-top-button { display: none; -} +} \ No newline at end of file