Skip to content

Commit

Permalink
Merge pull request #288 from cofacts/page-list-display
Browse files Browse the repository at this point in the history
Replace <ArticleItem> with more reusable card components.
Merge without review after 10 days since this PR is marked as reviewable.
  • Loading branch information
MrOrz authored Sep 16, 2020
2 parents 0e0a3a6 + ce0e7d7 commit 678f087
Show file tree
Hide file tree
Showing 17 changed files with 885 additions and 266 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules/*
.next
coverage
!.storybook
public/*
134 changes: 117 additions & 17 deletions components/ArticlePageLayout.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import gql from 'graphql-tag';
import { t, jt } from 'ttag';
import { t, jt, ngettext, msgid } from 'ttag';
import { useRouter } from 'next/router';
import Link from 'next/link';
import { useQuery } from '@apollo/react-hooks';

import Box from '@material-ui/core/Box';
Expand All @@ -11,8 +12,14 @@ import { makeStyles } from '@material-ui/core/styles';
import { ellipsis } from 'lib/text';
import useCurrentUser from 'lib/useCurrentUser';
import * as FILTERS from 'constants/articleFilters';
import ArticleItem from 'components/ListPageDisplays/ArticleItem';
import ListPageCards from 'components/ListPageDisplays/ListPageCards';
import ArticleCard from 'components/ListPageDisplays/ArticleCard';
import ListPageCard from 'components/ListPageDisplays/ListPageCard';
import ReplyItem from 'components/ListPageDisplays/ReplyItem';
import Infos from 'components/Infos';
import TimeInfo from 'components/Infos/TimeInfo';
import FeedDisplay from 'components/Subscribe/FeedDisplay';
import ExpandableText from 'components/ExpandableText';
import Filters from 'components/ListPageControls/Filters';
import ArticleStatusFilter from 'components/ListPageControls/ArticleStatusFilter';
import CategoryFilter from 'components/ListPageControls/CategoryFilter';
Expand All @@ -29,16 +36,29 @@ const LIST_ARTICLES = gql`
$orderBy: [ListArticleOrderBy]
$after: String
) {
ListArticles(filter: $filter, orderBy: $orderBy, after: $after, first: 10) {
ListArticles(filter: $filter, orderBy: $orderBy, after: $after, first: 25) {
edges {
node {
...ArticleItem
id
replyRequestCount
createdAt
text
articleReplies(status: NORMAL) {
reply {
id
...ReplyItem
}
...ReplyItemArticleReplyData
}
...ArticleCard
}
cursor
}
}
}
${ArticleItem.fragments.ArticleItem}
${ArticleCard.fragments.ArticleCard}
${ReplyItem.fragments.ReplyItem}
${ReplyItem.fragments.ReplyItemArticleReplyData}
`;

const LIST_STAT = gql`
Expand All @@ -56,13 +76,47 @@ const LIST_STAT = gql`
}
`;

const useStyles = makeStyles(() => ({
const useStyles = makeStyles(theme => ({
filters: {
margin: '12px 0',
},
articleList: {
padding: 0,
},
highlight: {
color: theme.palette.primary[500],
},
noStyleLink: {
// Canceling link styles
color: 'inherit',
textDecoration: 'none',
},
bustHoaxDivider: {
fontSize: theme.typography.htmlFontSize,
position: 'relative',
display: 'flex',
justifyContent: 'center',
padding: '12px 0',
'&:before': {
position: 'absolute',
top: '50%',
display: 'block',
height: '1px',
width: '100%',
backgroundColor: theme.palette.secondary[100],
content: '""',
},
'& a': {
position: 'relative',
flex: '1 1 shrink',
borderRadius: 30,
padding: '10px 26px',
textAlign: 'center',
backgroundColor: theme.palette.primary.main,
color: theme.palette.common.white,
zIndex: 2,
},
},
}));

/**
Expand Down Expand Up @@ -162,7 +216,6 @@ export function getQueryVars(query) {

function ArticlePageLayout({
title,
articleDisplayConfig = {},
defaultOrder = 'lastRequestedAt',
defaultFilters = [],
timeRangeKey = 'createdAt',
Expand All @@ -171,6 +224,12 @@ function ArticlePageLayout({
consider: true,
category: true,
},

// What "page" the <ArticlePageLayout> is used.
// FIXME: this is a temporary variable bridging the current <ArticlePageLayout> with
// future layout with no <ArticlePageLayout> at all.
//
page,
}) {
const classes = useStyles();
const { query } = useRouter();
Expand Down Expand Up @@ -258,16 +317,57 @@ function ArticlePageLayout({
listArticlesError.toString()
) : (
<>
<ul className={classes.articleList}>
{articleEdges.map(({ node }) => (
<ArticleItem
key={node.id}
article={node}
query={query.q}
{...articleDisplayConfig}
/>
))}
</ul>
<ListPageCards>
{/**
* FIXME: the "page" logic will be removed when ArticlePageLayout is splitted into
* each separate page component.
*/}
{articleEdges.map(({ node: article }) =>
page === 'replies' ? (
<ListPageCard key={article.id}>
<Infos>
<>
{ngettext(
msgid`${article.replyRequestCount} occurrence`,
`${article.replyRequestCount} occurrences`,
article.replyRequestCount
)}
</>
<TimeInfo time={article.createdAt}>
{timeAgo => t`First reported ${timeAgo} ago`}
</TimeInfo>
</Infos>
<ExpandableText lineClamp={2}>{article.text}</ExpandableText>

<div
className={classes.bustHoaxDivider}
data-ga="Bust hoax button"
>
<Link href="/article/[id]" as={`/article/${article.id}`}>
<a>{t`Bust Hoaxes`}</a>
</Link>
</div>

{article.articleReplies.map(({ reply, ...articleReply }) => (
<ReplyItem
key={reply.id}
articleReply={articleReply}
reply={reply}
/>
))}
</ListPageCard>
) : (
// This will be copied to pages/articles, pages/search and pages/hoax-for-you
// when we remove ArticlePageLayout.
//
<ArticleCard
key={article.id}
article={article}
query={query.q}
/>
)
)}
</ListPageCards>

<LoadMore
edges={articleEdges}
Expand Down
17 changes: 16 additions & 1 deletion components/Infos/Infos.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import Infos, { TimeInfo } from './';
import Infos, { TimeInfo, ReplyCountInfo } from './';
import { withKnobs, boolean } from '@storybook/addon-knobs';

export default {
Expand Down Expand Up @@ -47,3 +47,18 @@ export const WithTimeInfo = () => (
</TimeInfo>
</Infos>
);

export const WithReplyCountInfo = () => (
<Infos>
<ReplyCountInfo normalArticleReplies={[]} />
<ReplyCountInfo
normalArticleReplies={[
{ replyType: 'NOT_ARTICLE' },
{ replyType: 'NOT_ARTICLE' },
{ replyType: 'OPINIONATED' },
{ replyType: 'NOT_RUMOR' },
{ replyType: 'RUMOR' },
]}
/>
</Infos>
);
79 changes: 79 additions & 0 deletions components/Infos/ReplyCountInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import gql from 'graphql-tag';
import { ngettext, msgid } from 'ttag';
import { makeStyles } from '@material-ui/core/styles';
import { TYPE_ICON } from 'constants/replyType';
import Tooltip from 'components/Tooltip';

const useStyles = makeStyles(() => ({
opinions: {
display: 'flex',
},
opinion: {
display: 'flex',
alignItems: 'center',
'&:not(:first-child)': {
paddingLeft: 15,
},
'& > span:nth-child(2)': {
paddingLeft: 4,
},
},
optionIcon: {
fontSize: 14,
},
}));

function ReplyCountInfo({ normalArticleReplies }) {
const classes = useStyles();

const replyCount = normalArticleReplies.length;
const opinions = normalArticleReplies.reduce((result, { replyType }) => {
if (result[replyType]) {
result[replyType] += 1;
} else {
result[replyType] = 1;
}
return result;
}, {});

return (
<Tooltip
title={
replyCount === 0 ? (
''
) : (
<div className={classes.opinions}>
{Object.entries(opinions).map(([k, v]) => {
const IconComponent = TYPE_ICON[k];
return (
<span key={k} className={classes.opinion}>
<IconComponent className={classes.optionIcon} />
<span>{v}</span>
</span>
);
})}
</div>
)
}
arrow
>
<span>
{ngettext(
msgid`${replyCount} response`,
`${replyCount} responses`,
replyCount
)}
</span>
</Tooltip>
);
}

ReplyCountInfo.fragments = {
ReplyCountInfo: gql`
fragment NormalArticleReplyForReplyCountInfo on ArticleReply {
replyType
}
`,
};

export default ReplyCountInfo;
Loading

0 comments on commit 678f087

Please sign in to comment.