Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x-live-blog-post component #474

Merged
merged 36 commits into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4db4c21
Create skeleton x-content-body component
May 6, 2020
c1acef3
Add styling using Origami component
May 6, 2020
4058711
Run `bower install` on prepare
May 6, 2020
528e028
Display the current data structure of live blogs in x-content-block
tbergmen May 19, 2020
9a774ab
Use `content` prop over `textrendered`
May 19, 2020
c3a2108
Move share buttons into their own component
May 19, 2020
b2c76f4
Move time stamp into its own component
May 20, 2020
682fd7d
Rename props passed into component
May 28, 2020
e936654
Update x-content-block styles
tbergmen Jun 2, 2020
e8e6326
Remove unused variables
tbergmen Jun 5, 2020
70f097e
Breaking news tag styling updates
tbergmen Jun 8, 2020
b852266
Add publish time next to 'xx minutes ago' timestamp
tbergmen Jun 9, 2020
7f2ff65
Refactor time string code
tbergmen Jun 11, 2020
72d2aeb
Rename TimeStamp as Timestamp
tbergmen Jun 11, 2020
ed0961f
x-content-block timestamp formatting logic
tbergmen Jun 15, 2020
43a8ea7
Use string instead of object for publishedTimestamp
tbergmen Jun 15, 2020
c42c386
Remove id from article element
tbergmen Jun 16, 2020
07f06f1
Update storybook properties
tbergmen Jun 17, 2020
b4de6e4
Update readme
tbergmen Jun 17, 2020
6dac9fd
Use Node 10.x or greater
tbergmen Jun 18, 2020
c70d756
Update Readme with component description
tbergmen Jun 18, 2020
2bae68d
Add unit tests
tbergmen Jun 18, 2020
8eb917b
Use oSpacingByName for breaking news indicator element dimensions
tbergmen Jun 18, 2020
59af9d0
More unit tests
tbergmen Jun 19, 2020
621241f
Use o-date to render exact time
tbergmen Jun 19, 2020
8cb021f
Add descriptive aria-label to social media sharing links
tbergmen Jun 19, 2020
a790973
Fix unit tests
tbergmen Jun 19, 2020
05adef5
Refactor exact time display logic
tbergmen Jun 19, 2020
2ba20d3
Rename x-content-block to x-live-blog-post
tbergmen Jun 19, 2020
0dca0bd
Fix typo
tbergmen Jun 19, 2020
e4dd0b6
Updated x-live-blog-post readme with better description
tbergmen Jun 22, 2020
5c700d4
Add an option to hide share buttons
tbergmen Jun 22, 2020
f058595
Hide share buttons by default
tbergmen Jun 22, 2020
efee2ac
Add documentation about showShareButtons flag
tbergmen Jun 22, 2020
7833595
fix unit tests
tbergmen Jun 22, 2020
ff4af67
Explicitly set the default value of showShareButtons property
tbergmen Jun 22, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .storybook/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,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');
require('../components/x-live-blog-post/storybook/index.jsx');
}, module);
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

components/x-teaser @Financial-Times/content-discovery
components/x-teaser-timeline @Financial-Times/content-discovery
components/x-live-blog-post @Financial-Times/content-innovation
8 changes: 8 additions & 0 deletions components/x-live-blog-post/.bowerrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"registry": {
"search": [
"https://origami-bower-registry.ft.com",
"https://registry.bower.io"
]
}
}
3 changes: 3 additions & 0 deletions components/x-live-blog-post/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/
stories/
rollup.js
11 changes: 11 additions & 0 deletions components/x-live-blog-post/bower.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@financial-times/x-live-blog-post",
"description": "",
"main": "dist/LiveBlogPost.cjs.js",
"private": true,
"dependencies": {
"o-colors": "^5.2.4",
"o-spacing": "^2.0.4",
"o-typography": "^6.4.0"
}
}
38 changes: 38 additions & 0 deletions components/x-live-blog-post/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@financial-times/x-live-blog-post",
"version": "0.0.0",
"description": "",
"main": "dist/LiveBlogPost.cjs.js",
"module": "dist/LiveBlogPost.esm.js",
"browser": "dist/LiveBlogPost.es5.js",
"style": "dist/LiveBlogPost.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/x-engine": "file:../../packages/x-engine"
},
"devDependencies": {
"@financial-times/x-test-utils": "file:../../packages/x-test-utils",
"@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-live-blog-post",
"engines": {
"node": ">= 10.x"
},
"publishConfig": {
"access": "public"
}
}
47 changes: 47 additions & 0 deletions components/x-live-blog-post/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# x-live-blog-post

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.

```bash
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


## 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 { LiveBlogPost } from '@financial-times/x-live-blog-post';

// A == B == C
const a = LiveBlogPost(props);
const b = <LiveBlogPost {...props} />;
const c = React.createElement(LiveBlogPost, 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
--------------------|--------|----------------------------
`postId` | String | Unique id to reference the content
`title` | String | Title of the content
`content` | String | Body of the content
`isBreakingNews` | Bool | When `true` displays "breaking news" tag
`publishedTimestamp`| String | 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`
4 changes: 4 additions & 0 deletions components/x-live-blog-post/rollup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const xRollup = require('@financial-times/x-rollup');
const pkg = require('./package.json');

xRollup({ input: './src/LiveBlogPost.jsx', pkg });
31 changes: 31 additions & 0 deletions components/x-live-blog-post/src/LiveBlogPost.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { h } from '@financial-times/x-engine';
import ShareButtons from './ShareButtons';
import Timestamp from './Timestamp';
import styles from './LiveBlogPost.scss';

const LiveBlogPost = (props) => {
const {
postId,
title,
content,
publishedTimestamp,
isBreakingNews,
articleUrl,
showShareButtons = false,
} = props;

return (
<article className={styles['live-blog-post']} data-trackable="live-post">
<div className="live-blog-post__meta">
<Timestamp publishedTimestamp={publishedTimestamp} />
</div>
{isBreakingNews && <div className={styles['live-blog-post__breaking-news']}>Breaking news</div>}
<h1 className={styles['live-blog-post__title']}>{title}</h1>
<div className={styles['live-blog-post__body']} dangerouslySetInnerHTML={{ __html: content }} />
{showShareButtons &&
<ShareButtons postId={postId} articleUrl={articleUrl} title={title} />}
</article>
);
};

export { LiveBlogPost };
70 changes: 70 additions & 0 deletions components/x-live-blog-post/src/LiveBlogPost.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
@import 'o-typography/main';
@import 'o-spacing/main';
@import 'o-colors/main';

.live-blog-post {
border-bottom: 1px solid oColorsMix(black, paper, 20);
margin-top: oSpacingByName('s8');
color: oColorsMix(black, paper, 90);
}

.live-blog-post__title {
@include oTypographyDisplay($scale: 5);
margin-top: oSpacingByName('s4');
}

.live-blog-post__breaking-news + .live-blog-post__title {
margin-top: oSpacingByName('s1');
}

.live-blog-post__body {
@include oTypographySerif($scale: 1, $line-height: 1.55);
margin-top: oSpacingByName('s6');
}

.live-blog-post__body ul {
@include oTypographyList('unordered');
}

.live-blog-post__timestamp {
@include oTypographySans($scale:0, $weight: 'semibold');
font-size: 14px;
text-transform: uppercase;
}

.live-blog-post__timestamp-exact-time {
@include oTypographySans($scale:0, $weight: 'light');
font-size: 14px;
color: oColorsMix(black, paper, 50);
padding-left: oSpacingByName('s2');
}

.live-blog-post__timestamp-container:after {
content: '';
display: block;
width: oSpacingByName('s4');
border-bottom: 4px solid oColorsMix(black, paper, 90);
}

.live-blog-post__share-buttons {
margin-top: oSpacingByName('s6');
margin-bottom: oSpacingByName('s8');
}

.live-blog-post__breaking-news {
@include oTypographySans($scale:0);
font-size: 12px;
color: oColorsByName('crimson');
text-transform: uppercase;
margin-top: oSpacingByName('s4');
}

.live-blog-post__breaking-news:before {
content: '';
display: inline-block;
width: oSpacingByName('s2');
height: oSpacingByName('s2');
margin-right: oSpacingByName('s1');
border-radius: 50%;
background-color: oColorsByName('crimson');
}
52 changes: 52 additions & 0 deletions components/x-live-blog-post/src/ShareButtons.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { h } from '@financial-times/x-engine';
import styles from './LiveBlogPost.scss';

export default ({ postId, articleUrl, title }) => {
const shareUrl = articleUrl ? new URL(articleUrl) : null;
if (shareUrl) {
shareUrl.hash = `post-${postId}`;
}

const twitterUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&amp;text=${encodeURIComponent(title)}&amp;via=financialtimes`;
const facebookUrl = `http://www.facebook.com/sharer.php?u=${encodeURIComponent(shareUrl)}&amp;t=${encodeURIComponent(title)}`;
const linkedInUrl = `http://www.linkedin.com/shareArticle?mini=true&amp;url=${encodeURIComponent(shareUrl)}&amp;title=${encodeURIComponent(title)}&amp;source=Financial+Times`;

return (
<div className={styles['live-blog-post__share-buttons']}>
<div
data-o-component="o-share"
data-o-share-location={`live-blog-post-${postId}`}
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">
<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">
<a
className="o-share__icon o-share__icon--facebook"
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>
</a>
</li>
<li className="o-share__action" data-share="linkedin">
<a
className="o-share__icon o-share__icon--linkedin"
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>
</a>
</li>
</ul>
</div>
</div>
);
};
45 changes: 45 additions & 0 deletions components/x-live-blog-post/src/Timestamp.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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();

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;
} 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;
}

return (
<div className={styles['live-blog-post__timestamp-container']}>
<time
data-o-component="o-date"
className={`o-date ${styles['live-blog-post__timestamp']}`}
itemProp="datePublished"
data-o-date-format={format}
dateTime={publishedTimestamp}>{formatted}
</time>
{showExactTime && (
<time
data-o-component="o-date"
className={`o-date ${styles['live-blog-post__timestamp-exact-time']}`}
itemProp="exactTime"
data-o-date-format="HH:mm"
dateTime={publishedTimestamp}>{formatted}
</time>
)}
</div>
);
};
62 changes: 62 additions & 0 deletions components/x-live-blog-post/src/__tests__/LiveBlogPost.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const { h } = require('@financial-times/x-engine');
const { mount } = require('@financial-times/x-test-utils/enzyme');

import { LiveBlogPost } from '../LiveBlogPost';

const breakingNews = {
postId: '12345',
title: 'Test',
content: '<p>Test</p>',
publishedTimestamp: new Date().toISOString(),
isBreakingNews: true,
articleUrl: 'Https://www.ft.com',
showShareButtons: true
};

const regularPost = {
postId: '12345',
title: 'Test title',
content: '<p><i>Test body</i></p>',
publishedTimestamp: new Date().toISOString(),
isBreakingNews: false,
articleUrl: 'Https://www.ft.com',
showShareButtons: true
}

describe('x-live-blog-post', () => {
it('renders title', () => {
const liveBlogPost = mount(<LiveBlogPost {...regularPost} />);

expect(liveBlogPost.html()).toContain('Test title');
});

it('renders timestamp', () => {
const liveBlogPost = mount(<LiveBlogPost {...regularPost} />);

expect(liveBlogPost.html()).toContain(regularPost.publishedTimestamp);
});

it('renders sharing buttons', () => {
const liveBlogPost = mount(<LiveBlogPost {...regularPost} />);

expect(liveBlogPost.html()).toContain('o-share__icon--linkedin');
});

it('renders breaking news tag when the post is a breaking news', () => {
const liveBlogPost = mount(<LiveBlogPost {...breakingNews} />);

expect(liveBlogPost.html()).toContain('Breaking news');
});

it('does not render breaking news tag when the post is not breaking news', () => {
const liveBlogPost = mount(<LiveBlogPost {...regularPost} />);

expect(liveBlogPost.html()).not.toContain('Breaking news');
});

it('does not escape content html', () => {
const liveBlogPost = mount(<LiveBlogPost {...regularPost} />);

expect(liveBlogPost.html()).toContain('<p><i>Test body</i></p>');
});
});
Loading