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 e2fd93ac5..f56e95263 100644 --- a/__tests__/__snapshots__/snapshots.test.js.snap +++ b/__tests__/__snapshots__/snapshots.test.js.snap @@ -5528,3 +5528,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 f0908e0d8..d6980330c 100644 --- a/tools/x-storybook/package.json +++ b/tools/x-storybook/package.json @@ -21,6 +21,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": "^3.4.4", "@storybook/addon-viewport": "^3.4.4", "@storybook/addons": "^3.4.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;