diff --git a/news/components/ArticleMeta.js b/news/components/ArticleMeta.js
new file mode 100644
index 0000000000..4d1d527b49
--- /dev/null
+++ b/news/components/ArticleMeta.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Helmet from 'react-helmet';
+import differenceInMinutes from 'date-fns/difference_in_minutes';
+import differenceInHours from 'date-fns/difference_in_hours';
+import differenceInDays from 'date-fns/difference_in_calendar_days';
+import format from 'date-fns/format';
+
+const propTypes = {
+ article: PropTypes.object
+};
+
+const styles = `
+
+.meta-wrapper {
+ padding-top: 10px;
+}
+
+.meta-wrapper span,
+.meta-wrapper a {
+ font-size: 16px;
+}
+
+.meta-item {
+ margin-right: 20px;
+}
+
+`;
+
+function pluralise(singular, count) {
+ return `${singular}${count === 1 ? '' : 's'}`;
+}
+
+function getTimeString(pubDate) {
+ const now = new Date(Date.now());
+ const minuteDiff = differenceInMinutes(now, pubDate);
+ console.log(typeof minuteDiff);
+ if (minuteDiff < 60) {
+ return `${minuteDiff} ${pluralise('minute', minuteDiff)} ago`;
+ }
+ const hourDiff = differenceInHours(now, pubDate);
+ if (hourDiff < 24) {
+ return `${hourDiff} ${pluralise('hour', hourDiff)} ago`;
+ }
+ const dayDiff = differenceInDays(now, pubDate);
+ if (dayDiff < 8) {
+ return `${dayDiff} ${pluralise('day', dayDiff)} ago`;
+ }
+
+ if (dayDiff < 365) {
+ return format(pubDate, 'MMM D');
+ }
+
+ return format(pubDate, 'MMM D YYYY');
+}
+
+function ArticleMeta({
+ article: { viewCount, author, meta, firstPublishedDate }
+}) {
+ return (
+
+
+
+
+
+ By {author.name}
+ {getTimeString(firstPublishedDate)}
+ {`${meta.readTime} minute read`}
+ {viewCount >= 100 ? (
+ {`${viewCount} views`}
+ ) : null}
+
+
+ );
+}
+
+ArticleMeta.displayName = 'ArticleMeta';
+ArticleMeta.propTypes = propTypes;
+
+export default ArticleMeta;
diff --git a/news/routes/Featured/Featured.js b/news/routes/Featured/Featured.js
index 0e2fa41c86..20da0a11ab 100644
--- a/news/routes/Featured/Featured.js
+++ b/news/routes/Featured/Featured.js
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { Image } from 'react-bootstrap';
import Helmet from 'react-helmet';
@@ -6,17 +7,36 @@ import Helmet from 'react-helmet';
import { getFeaturedList } from '../../utils/ajax';
import { Loader, Spacer } from '../../../common/app/helperComponents';
import BannerWide from '../../components/BannerWide';
+import ArticleMeta from '../../components/ArticleMeta';
-const propTypes = {};
+const propTypes = {
+ history: PropTypes.shape({
+ push: PropTypes.func.isRequired
+ })
+};
const styles = `
.featured-list {
list-style: none;
padding-left: 0;
+ margin-top: 40px;
+ }
+
+ .featured-list-item {
+ padding-bottom: 20px;
}
.featured-list-item .title {
color: #333;
+ padding-bottom: 20px;
+ }
+
+ .featured-list-item a {
+ padding-top: 5px;
+ }
+
+ .featured-list-image {
+ margin: 0 auto;
}
.featured-list-item a:hover,
@@ -29,19 +49,6 @@ const styles = `
.featured-list-item a:focus > .meta-wrapper {
color: #006400;
}
-
- .meta-wrapper {
- display: flex;
- justify-content: space-between;
- }
-
- .meta-wrapper span {
- font-size: 14px;
- }
-
- .meta-stats {
- text-align: end;
- }
`;
class Featured extends Component {
@@ -100,16 +107,24 @@ class Featured extends Component {
'--',
article.shortId
);
- const { featureImage, shortId, author, title, meta } = article;
+ const { featureImage, shortId, title } = article;
return (
- {featureImage && featureImage.src ? () : ()}
-
+
{title}
-
- By {author.name}
- {`${meta.readTime} minute read`}
{`${article.viewCount} views`}
-
+ {featureImage && featureImage.src ? (
+
+ ) : (
+
+ )}
+
@@ -134,12 +149,7 @@ class Featured extends Component {
- Welcome to freeCodeCamp News
-
- Featured Articles
-
- {this.renderFeatured(featuredList)}
-
+ {this.renderFeatured(featuredList)}
);
}
diff --git a/news/routes/Show/Show.js b/news/routes/Show/Show.js
index f9e44c5224..12421f288e 100644
--- a/news/routes/Show/Show.js
+++ b/news/routes/Show/Show.js
@@ -14,7 +14,8 @@ const propTypes = {
push: PropTypes.func.isRequired
}),
location: PropTypes.shape({
- state: PropTypes.object
+ state: PropTypes.object,
+ pathname: PropTypes.string
}),
match: PropTypes.shape({
params: PropTypes.shape({
@@ -33,6 +34,20 @@ const youtubeOpts = {
const styles = `
+ .show-article figure {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .show-article figcaption > * {
+ font-size: 16px;
+ }
+
+ .show-article figcaption {
+ padding-top: 5px;
+ }
+
.feature-image-wrapper {
padding-top: 32px;
}
@@ -70,6 +85,7 @@ class ShowArticle extends Component {
}
componentDidMount() {
+ window.scrollTo(0, 0);
const {
history,
match: {
@@ -142,13 +158,8 @@ class ShowArticle extends Component {
render() {
const {
fetchState: { pending, complete, errored },
- currentArticle: {
- title,
- renderableContent,
- youtubeId,
- featureImage,
- author
- }
+ currentArticle: { title, renderableContent, youtubeId, featureImage },
+ currentArticle
} = this.state;
if (pending || !complete) {
return ;
@@ -159,11 +170,11 @@ class ShowArticle extends Component {
}
return (
-
+
-
+
{title}
@@ -179,7 +190,6 @@ class ShowArticle extends Component {
) : null}
-
{youtubeId ? (
diff --git a/news/routes/Show/components/Author.js b/news/routes/Show/components/Author.js
index 91e7fdf5a7..2f69893d90 100644
--- a/news/routes/Show/components/Author.js
+++ b/news/routes/Show/components/Author.js
@@ -2,8 +2,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
+import ArticleMeta from '../../../components/ArticleMeta';
+
const propTypes = {
- author: PropTypes.objectOf(PropTypes.string)
+ article: PropTypes.shape({
+ author: PropTypes.objectOf(PropTypes.string)
+ })
};
const styles = `
@@ -33,7 +37,10 @@ const styles = `
}
`;
-function Author({ author: { name, avatar, bio } }) {
+function Author({ article }) {
+ const {
+ author: { avatar, bio }
+ } = article;
return (
@@ -41,9 +48,7 @@ function Author({ author: { name, avatar, bio } }) {