Skip to content

Commit 78f74ab

Browse files
committed
Implemented new feeds for job, ask, show. Comments now work deeply nested on a news item. Comments returned from the HN api as dead, deleted or null are rejected as promises and become undefined, which gets array filtered before returning the GraphQL result. Made the GraphQL type for some comment properties non-nullable. Everything nullable in the graphql schema should be handled when null on the client side, and everything non-nullable in the schema should be handled in resolvers.
1 parent 016bba6 commit 78f74ab

21 files changed

+538
-131
lines changed

src/components/Comments.js

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,40 @@ import Comment from './Comment';
66

77
const Comments = (props) => {
88
const rows = [];
9+
function buildComment(comment, indent) {
10+
return (
11+
<Comment
12+
key={comment.id}
13+
parentId={comment.parent}
14+
indentationLevel={indent}
15+
{...comment}
16+
/>
17+
);
18+
}
919
props.newsItem.comments.forEach((rootComment) => {
10-
rows.push(<Comment
11-
key={rootComment.id}
12-
parentId={props.newsItem.id}
13-
indentationLevel={0}
14-
{...rootComment}
15-
/>);
16-
rootComment.comments.forEach((one) => {
17-
rows.push(<Comment
18-
key={one.id}
19-
parentId={one.parent}
20-
indentationLevel={1}
21-
{...one}
22-
/>);
20+
rows.push(buildComment(rootComment, 0));
21+
22+
rootComment.comments.forEach((commentOne) => {
23+
rows.push(buildComment(commentOne, 1));
24+
25+
commentOne.comments.forEach((commentTwo) => {
26+
rows.push(buildComment((commentTwo), 2));
27+
28+
commentTwo.comments.forEach((commentThree) => {
29+
rows.push(buildComment(commentThree, 3));
30+
31+
commentThree.comments.forEach((commentFour) => {
32+
rows.push(buildComment(commentFour, 4));
33+
34+
commentFour.comments.forEach((commentFive) => {
35+
rows.push(buildComment(commentFive, 5));
36+
});
37+
});
38+
});
39+
});
2340
});
2441
});
25-
42+
2643
return (
2744
<table className="comment-tree" style={{ border: '0' }} >
2845
<tbody>
@@ -48,19 +65,24 @@ Comments.fragments = {
4865
id,
4966
comments {
5067
id,
68+
comments {
69+
id,
70+
comments {
71+
id,
72+
comments {
73+
id,
74+
...Comment
75+
}
76+
...Comment
77+
}
78+
...Comment
79+
}
5180
...Comment
5281
}
5382
...Comment
5483
}
5584
${Comment.fragments.comment}
5685
`,
5786
};
58-
// comments {
59-
// id
60-
// ...Comment
61-
// }
62-
// ...Comment
63-
// comments {
64-
// ...Comment
65-
// }
87+
6688
export default Comments;

src/components/HeaderNav.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,34 @@ const HeaderNav = props => (
1414
{props.userId && <Link prefetch href="/newswelcome"><a>welcome</a></Link>}
1515
{props.userId && ' | '}
1616
<Link prefetch href="/newest">
17-
<a>new</a>
17+
<a className={props.currentURL === '/newest' ? 'topsel' : ''}>new</a>
1818
</Link>
1919
{props.userId && ' | '}
20-
{props.userId && <Link prefetch href={`/threads?id=${props.userId}`}><a>threads</a></Link>}
20+
{
21+
props.userId &&
22+
<Link prefetch href={`/threads?id=${props.userId}`}>
23+
<a className={props.currentURL === '/threads' ? 'topsel' : ''}>threads</a>
24+
</Link>
25+
}
2126
{' | '}
2227
<Link prefetch href="/newcomments">
23-
<a>comments</a>
28+
<a className={props.currentURL === '/newcomments' ? 'topsel' : ''}>comments</a>
2429
</Link>
2530
{' | '}
2631
<Link prefetch href="/show">
27-
<a>show</a>
32+
<a className={props.currentURL === '/show' ? 'topsel' : ''}>show</a>
2833
</Link>
2934
{' | '}
3035
<Link prefetch href="/ask">
31-
<a>ask</a>
36+
<a className={props.currentURL === '/ask' ? 'topsel' : ''}>ask</a>
3237
</Link>
3338
{' | '}
3439
<Link prefetch href="/jobs">
35-
<a>jobs</a>
40+
<a className={props.currentURL === '/jobs' ? 'topsel' : ''}>jobs</a>
3641
</Link>
3742
{' | '}
3843
<Link prefetch href="/submit">
39-
<a>submit</a>
44+
<a className={props.currentURL === '/submit' ? 'topsel' : ''}>submit</a>
4045
</Link>
4146
</span>
4247
:
@@ -49,6 +54,7 @@ HeaderNav.defaultProps = {
4954
};
5055
HeaderNav.propTypes = {
5156
userId: PropTypes.string,
57+
currentURL: PropTypes.string.isRequired,
5258
isNavVisible: PropTypes.bool.isRequired,
5359
title: PropTypes.string.isRequired,
5460
};

src/components/NewsDetail.js

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ class NewsDetail extends Component {
1616
points: PropTypes.number.isRequired,
1717
isPostScrutinyVisible: PropTypes.bool,
1818
isFavoriteVisible: PropTypes.bool,
19+
isJobListing: PropTypes.bool,
1920
}
2021
static defaultProps = {
2122
isFavoriteVisible: true,
2223
isPostScrutinyVisible: false,
24+
isJobListing: false,
2325
}
2426
static fragments = {
2527
newsItem: gql`
@@ -42,56 +44,70 @@ class NewsDetail extends Component {
4244
}
4345
render() {
4446
return (
45-
<tr>
46-
<td colSpan="2" />
47-
<td className="subtext">
48-
<span className="score">{this.props.points} points</span>
49-
{' by '}
50-
<Link prefetch href={`/user?id=${this.props.submitterId}`}>
51-
<a className="hnuser">
52-
{this.props.submitterId}
47+
this.props.isJobListing ?
48+
<tr>
49+
<td colSpan="2" />
50+
<td className="subtext">
51+
<span className="age">
52+
<Link prefetch href={`/item?id=${this.props.id}`}>
53+
<a>
54+
{convertNumberToTimeAgo(this.props.creationTime)}
55+
</a>
56+
</Link>
57+
</span>
58+
</td>
59+
</tr>
60+
:
61+
<tr>
62+
<td colSpan="2" />
63+
<td className="subtext">
64+
<span className="score">{this.props.points} points</span>
65+
{' by '}
66+
<Link prefetch href={`/user?id=${this.props.submitterId}`}>
67+
<a className="hnuser">
68+
{this.props.submitterId}
69+
</a>
70+
</Link>
71+
{' '}
72+
<span className="age">
73+
<Link prefetch href={`/item?id=${this.props.id}`}>
74+
<a>
75+
{convertNumberToTimeAgo(this.props.creationTime)}
76+
</a>
77+
</Link>
78+
</span>
79+
{' | '}
80+
<a href="javascript:void(0)" onClick={this.hidestory}>
81+
hide
5382
</a>
54-
</Link>
55-
{' '}
56-
<span className="age">
83+
{
84+
this.props.isPostScrutinyVisible &&
85+
<span>
86+
{' | '}
87+
<a href="https://hn.algolia.com/?query=Sublime%20Text%203.0&sort=byDate&dateRange=all&type=story&storyText=false&prefix&page=0" onClick={this.hidestory}>
88+
past
89+
</a>
90+
{' | '}
91+
<a href="https://www.google.com/search?q=Sublime%20Text%203.0" onClick={this.hidestory}>
92+
web
93+
</a>
94+
</span>
95+
}
96+
{' | '}
5797
<Link prefetch href={`/item?id=${this.props.id}`}>
5898
<a>
59-
{convertNumberToTimeAgo(this.props.creationTime)}
99+
{(() => {
100+
switch (this.props.commentCount) {
101+
case 0: return 'discuss';
102+
case 1: return '1 comment';
103+
default: return `${this.props.commentCount} comments`;
104+
}
105+
})()}
60106
</a>
61107
</Link>
62-
</span>
63-
{' | '}
64-
<a href="javascript:void(0)" onClick={this.hidestory}>
65-
hide
66-
</a>
67-
{
68-
this.props.isPostScrutinyVisible &&
69-
<span>
70-
{' | '}
71-
<a href="https://hn.algolia.com/?query=Sublime%20Text%203.0&sort=byDate&dateRange=all&type=story&storyText=false&prefix&page=0" onClick={this.hidestory}>
72-
past
73-
</a>
74-
{' | '}
75-
<a href="https://www.google.com/search?q=Sublime%20Text%203.0" onClick={this.hidestory}>
76-
web
77-
</a>
78-
</span>
79-
}
80-
{' | '}
81-
<Link prefetch href={`/item?id=${this.props.id}`}>
82-
<a>
83-
{(() => {
84-
switch (this.props.commentCount) {
85-
case 0: return 'discuss';
86-
case 1: return '1 comment';
87-
default: return `${this.props.commentCount} comments`;
88-
}
89-
})()}
90-
</a>
91-
</Link>
92-
{this.props.isFavoriteVisible && ' | favorite'}
93-
</td>
94-
</tr>
108+
{this.props.isFavoriteVisible && ' | favorite'}
109+
</td>
110+
</tr>
95111
);
96112
}
97113
}

src/components/NewsFeed.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import NewsDetail from './NewsDetail';
88
const NewsFeed = (props) => {
99
// props.newsItems.sort((a, b) => (a.rank - b.rank));
1010
const rows = [];
11+
if (props.notice) rows.push(...props.notice);
1112
props.newsItems.forEach((newsItem, index) => {
1213
rows.push(
1314
<NewsTitle
1415
key={`${newsItem.id.toString()}title`}
15-
isRankVisible={true}
16+
isRankVisible={props.isRankVisible}
17+
isUpvoteVisible={props.isUpvoteVisible}
1618
rank={index}
1719
{...newsItem}
1820
/>,
@@ -22,6 +24,7 @@ const NewsFeed = (props) => {
2224
key={`${newsItem.id.toString()}detail`}
2325
isFavoriteVisible={false}
2426
isPostScrutinyVisible={props.isPostScrutinyVisible}
27+
isJobListing={props.isJobListing}
2528
{...newsItem}
2629
/>,
2730
);
@@ -58,6 +61,10 @@ const NewsFeed = (props) => {
5861
};
5962
NewsFeed.defaultProps = {
6063
isPostScrutinyVisible: false,
64+
isJobListing: false,
65+
isRankVisible: true,
66+
isUpvoteVisible: true,
67+
notice: null,
6168
};
6269
NewsFeed.propTypes = {
6370
isPostScrutinyVisible: PropTypes.bool,
@@ -71,6 +78,10 @@ NewsFeed.propTypes = {
7178
commentCount: PropTypes.number.isRequired,
7279
points: PropTypes.number.isRequired,
7380
})).isRequired,
81+
notice: PropTypes.arrayOf(PropTypes.element),
82+
isJobListing: PropTypes.bool,
83+
isRankVisible: PropTypes.bool,
84+
isUpvoteVisible: PropTypes.bool,
7485
currentURL: PropTypes.string.isRequired,
7586
};
7687
NewsFeed.fragments = {

src/components/NewsFeedWithApolloRenderer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import PropTypes from 'prop-types';
33

44
import NewsFeed from './NewsFeed';
55

6-
export default ({ data: { loading, error, feed }, currentURL }) => {
6+
export default ({ data: { loading, error, feed }, options }) => {
77
if (error) return <tr><td>Error loading news items.</td></tr>;
88
if (feed && feed.length) {
99
return (
10-
<NewsFeed newsItems={feed} currentURL={currentURL} />
10+
<NewsFeed newsItems={feed} {...options} />
1111
);
1212
}
1313
return <tr><td>Loading</td></tr>;

src/components/NewsTitle.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ class NewsTitle extends Component {
1111
url: PropTypes.string,
1212
rank: PropTypes.number,
1313
isRankVisible: PropTypes.bool,
14+
isUpvoteVisible: PropTypes.bool,
1415
}
1516
static defaultProps = {
1617
// text: undefined,
1718
url: undefined,
1819
rank: undefined,
1920
isRankVisible: true,
21+
isUpvoteVisible: true,
2022
}
2123
static fragments = {
2224
newsItem: gql`
@@ -43,7 +45,12 @@ class NewsTitle extends Component {
4345
</td>
4446
<td style={{ verticalAlign: 'top' }} className="votelinks">
4547
<center>
46-
<a onClick={this.upvote} href="vote?id=15077519&amp;how=up&amp;auth=b73e5ad6975f51978fed805f4c3c079e9516fe1d&amp; goto=news"><div className="votearrow" title="upvote" /></a>
48+
{
49+
this.props.isUpvoteVisible &&
50+
<a onClick={this.upvote} href="vote?id=15077519&amp;how=up&amp;auth=b73e5ad6975f51978fed805f4c3c079e9516fe1d&amp; goto=news">
51+
<div className="votearrow" title="upvote" />
52+
</a>
53+
}
4754
</center>
4855
</td>
4956
<td className="title">

src/data/HNDataAPI.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export function fetchComment(id) {
6060
return new Promise((resolve, reject) => {
6161
api.child(`item/${id}`).once('value', (itemSnapshot) => {
6262
const item = itemSnapshot.val();
63-
if (item !== null && !item.deleted) {
63+
if (item !== null && !item.deleted && !item.dead) {
6464
const comment = {
6565
id: item.id,
6666
creationTime: item.time * 1000,
@@ -107,17 +107,15 @@ const rebuildFeed = (feedType) => {
107107

108108
/* BEGIN SEED DATA */
109109

110-
export function seedCache() {
110+
export function seedCache(delay) {
111111
// TODO: Build sample cache then seed
112-
logger('Seeding cache');
113-
function delayedSeed() {
112+
logger(`Waiting ${delay} ms before seeding the app with data.`);
113+
setTimeout(() => {
114+
logger('Seeding cache');
114115
['top', 'new', 'show', 'ask', 'job'].forEach((feedType) => {
115116
rebuildFeed(feedType);
116117
});
117-
}
118-
119-
logger('Waiting 1 min before seeding the app with data.');
120-
setTimeout(delayedSeed, 1000 * 60 * 1);
121-
// Delay seeding the cache so we don't spam using Nodemon
118+
}, delay);
119+
// Delay seeding the cache so we don't spam in dev
122120
}
123121
/* END SEED DATA */

0 commit comments

Comments
 (0)