fix(layout): Adjust layout
This commit is contained in:
committed by
mrugesh mohapatra
parent
9cdb0ec7a2
commit
fc19bf4fd3
80
news/components/ArticleMeta.js
Normal file
80
news/components/ArticleMeta.js
Normal file
@ -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 (
|
||||||
|
<div className='meta-wrapper'>
|
||||||
|
<Helmet>
|
||||||
|
<style>{styles}</style>
|
||||||
|
</Helmet>
|
||||||
|
<div className='meta-item-wrapper'>
|
||||||
|
<span className='meta-item'>By {author.name}</span>
|
||||||
|
<span className='meta-item'>{getTimeString(firstPublishedDate)}</span>
|
||||||
|
<span className='meta-item'>{`${meta.readTime} minute read`}</span>
|
||||||
|
{viewCount >= 100 ? (
|
||||||
|
<span className='meta-item'>{`${viewCount} views`}</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArticleMeta.displayName = 'ArticleMeta';
|
||||||
|
ArticleMeta.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default ArticleMeta;
|
@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { Image } from 'react-bootstrap';
|
import { Image } from 'react-bootstrap';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
@ -6,17 +7,36 @@ import Helmet from 'react-helmet';
|
|||||||
import { getFeaturedList } from '../../utils/ajax';
|
import { getFeaturedList } from '../../utils/ajax';
|
||||||
import { Loader, Spacer } from '../../../common/app/helperComponents';
|
import { Loader, Spacer } from '../../../common/app/helperComponents';
|
||||||
import BannerWide from '../../components/BannerWide';
|
import BannerWide from '../../components/BannerWide';
|
||||||
|
import ArticleMeta from '../../components/ArticleMeta';
|
||||||
|
|
||||||
const propTypes = {};
|
const propTypes = {
|
||||||
|
history: PropTypes.shape({
|
||||||
|
push: PropTypes.func.isRequired
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
const styles = `
|
const styles = `
|
||||||
.featured-list {
|
.featured-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-list-item {
|
||||||
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.featured-list-item .title {
|
.featured-list-item .title {
|
||||||
color: #333;
|
color: #333;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-list-item a {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-list-image {
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.featured-list-item a:hover,
|
.featured-list-item a:hover,
|
||||||
@ -29,19 +49,6 @@ const styles = `
|
|||||||
.featured-list-item a:focus > .meta-wrapper {
|
.featured-list-item a:focus > .meta-wrapper {
|
||||||
color: #006400;
|
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 {
|
class Featured extends Component {
|
||||||
@ -100,16 +107,24 @@ class Featured extends Component {
|
|||||||
'--',
|
'--',
|
||||||
article.shortId
|
article.shortId
|
||||||
);
|
);
|
||||||
const { featureImage, shortId, author, title, meta } = article;
|
const { featureImage, shortId, title } = article;
|
||||||
return (
|
return (
|
||||||
<li className='featured-list-item' key={shortId}>
|
<li className='featured-list-item' key={shortId}>
|
||||||
{featureImage && featureImage.src ? (<Image responsive={true} src={featureImage.src} />) : (<BannerWide />)}
|
<a
|
||||||
<a href={slug} onClick={this.createHandleArticleClick(slug, article)}>
|
href={'/news' + slug}
|
||||||
|
onClick={this.createHandleArticleClick(slug, article)}
|
||||||
|
>
|
||||||
<h3 className='title'>{title}</h3>
|
<h3 className='title'>{title}</h3>
|
||||||
<div className='meta-wrapper'>
|
{featureImage && featureImage.src ? (
|
||||||
<span>By {author.name}</span>
|
<Image
|
||||||
<span className='meta-stats'>{`${meta.readTime} minute read`}<br />{`${article.viewCount} views`}</span>
|
className='featured-list-image'
|
||||||
</div>
|
responsive={true}
|
||||||
|
src={featureImage.src}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BannerWide />
|
||||||
|
)}
|
||||||
|
<ArticleMeta article={article} />
|
||||||
</a>
|
</a>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
</li>
|
</li>
|
||||||
@ -134,12 +149,7 @@ class Featured extends Component {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<style>{styles}</style>
|
<style>{styles}</style>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<h2>Welcome to freeCodeCamp News</h2>
|
<ul className='featured-list'>{this.renderFeatured(featuredList)}</ul>
|
||||||
<hr />
|
|
||||||
<h2>Featured Articles</h2>
|
|
||||||
<ul className='featured-list'>
|
|
||||||
{this.renderFeatured(featuredList)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ const propTypes = {
|
|||||||
push: PropTypes.func.isRequired
|
push: PropTypes.func.isRequired
|
||||||
}),
|
}),
|
||||||
location: PropTypes.shape({
|
location: PropTypes.shape({
|
||||||
state: PropTypes.object
|
state: PropTypes.object,
|
||||||
|
pathname: PropTypes.string
|
||||||
}),
|
}),
|
||||||
match: PropTypes.shape({
|
match: PropTypes.shape({
|
||||||
params: PropTypes.shape({
|
params: PropTypes.shape({
|
||||||
@ -33,6 +34,20 @@ const youtubeOpts = {
|
|||||||
|
|
||||||
const styles = `
|
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 {
|
.feature-image-wrapper {
|
||||||
padding-top: 32px;
|
padding-top: 32px;
|
||||||
}
|
}
|
||||||
@ -70,6 +85,7 @@ class ShowArticle extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
const {
|
const {
|
||||||
history,
|
history,
|
||||||
match: {
|
match: {
|
||||||
@ -142,13 +158,8 @@ class ShowArticle extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
fetchState: { pending, complete, errored },
|
fetchState: { pending, complete, errored },
|
||||||
currentArticle: {
|
currentArticle: { title, renderableContent, youtubeId, featureImage },
|
||||||
title,
|
currentArticle
|
||||||
renderableContent,
|
|
||||||
youtubeId,
|
|
||||||
featureImage,
|
|
||||||
author
|
|
||||||
}
|
|
||||||
} = this.state;
|
} = this.state;
|
||||||
if (pending || !complete) {
|
if (pending || !complete) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
@ -159,11 +170,11 @@ class ShowArticle extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article>
|
<article className='show-article'>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<style>{styles}</style>
|
<style>{styles}</style>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Author author={author} />
|
<Author article={currentArticle} />
|
||||||
<h2>{title}</h2>
|
<h2>{title}</h2>
|
||||||
<div className='feature-image-wrapper'>
|
<div className='feature-image-wrapper'>
|
||||||
<figure>
|
<figure>
|
||||||
@ -179,7 +190,6 @@ class ShowArticle extends Component {
|
|||||||
) : null}
|
) : null}
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: renderableContent }} />
|
<div dangerouslySetInnerHTML={{ __html: renderableContent }} />
|
||||||
<div className='youtube-wrapper'>
|
<div className='youtube-wrapper'>
|
||||||
{youtubeId ? (
|
{youtubeId ? (
|
||||||
|
@ -2,8 +2,12 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
|
import ArticleMeta from '../../../components/ArticleMeta';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
author: PropTypes.objectOf(PropTypes.string)
|
article: PropTypes.shape({
|
||||||
|
author: PropTypes.objectOf(PropTypes.string)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = `
|
const styles = `
|
||||||
@ -33,7 +37,10 @@ const styles = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function Author({ author: { name, avatar, bio } }) {
|
function Author({ article }) {
|
||||||
|
const {
|
||||||
|
author: { avatar, bio }
|
||||||
|
} = article;
|
||||||
return (
|
return (
|
||||||
<div className='author-block'>
|
<div className='author-block'>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -41,9 +48,7 @@ function Author({ author: { name, avatar, bio } }) {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
<img height='50px' src={avatar} />
|
<img height='50px' src={avatar} />
|
||||||
<div className='author-bio'>
|
<div className='author-bio'>
|
||||||
<a href='https://www.freecodecamp.org/quincylarson'>
|
<ArticleMeta article={article} />
|
||||||
<span>By {name}</span>
|
|
||||||
</a>
|
|
||||||
<span>{bio.slice(0, 101)}</span>
|
<span>{bio.slice(0, 101)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user