Skip to content

Commit 895a47d

Browse files
committed
Implement showing post thumbnail
1 parent 8472df7 commit 895a47d

File tree

6 files changed

+135
-4
lines changed

6 files changed

+135
-4
lines changed

src/components/post/PostHead.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import styled from 'styled-components';
33
import VelogResponsive from '../velog/VelogResponsive';
44
import palette from '../../lib/styles/palette';
55
import { formatDate } from '../../lib/utils';
6+
import PostTags from './PostTags';
67

78
const PostHeadBlock = styled(VelogResponsive)`
89
margin-top: 5.5rem;
@@ -30,14 +31,34 @@ const SubInfo = styled.div`
3031
}
3132
`;
3233

34+
const Thumbnail = styled.img`
35+
max-height: 100vh;
36+
max-width: 100%;
37+
width: auto;
38+
margin: 0 auto;
39+
height: auto;
40+
object-fit: contain;
41+
display: block;
42+
margin-top: 2rem;
43+
`;
44+
3345
export interface PostHeadProps {
3446
title: string;
3547
tags: string[];
3648
username: string;
3749
date: string;
50+
thumbnail: string | null;
51+
hideThumbnail: boolean;
3852
}
3953

40-
const PostHead: React.FC<PostHeadProps> = ({ title, username, date }) => {
54+
const PostHead: React.FC<PostHeadProps> = ({
55+
title,
56+
username,
57+
date,
58+
tags,
59+
hideThumbnail,
60+
thumbnail,
61+
}) => {
4162
return (
4263
<PostHeadBlock>
4364
<h1>{title}</h1>
@@ -46,6 +67,10 @@ const PostHead: React.FC<PostHeadProps> = ({ title, username, date }) => {
4667
<span className="separator">&middot;</span>
4768
<span>{formatDate(date)}</span>
4869
</SubInfo>
70+
<PostTags tags={tags} />
71+
{!hideThumbnail && thumbnail && (
72+
<Thumbnail src={thumbnail} alt="post-thumbnail" />
73+
)}
4974
</PostHeadBlock>
5075
);
5176
};

src/components/post/PostTags.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import * as React from 'react';
2+
import styled from 'styled-components';
3+
import { Link } from 'react-router-dom';
4+
import palette from '../../lib/styles/palette';
5+
6+
const PostTagsBlock = styled.div`
7+
margin-top: 0.875rem;
8+
margin-bottom: -0.875rem;
9+
`;
10+
11+
const Tag = styled(Link)`
12+
margin-bottom: 0.875rem;
13+
background: ${palette.gray1};
14+
padding-left: 1rem;
15+
padding-right: 1rem;
16+
height: 2rem;
17+
border-radius: 1rem;
18+
display: inline-flex;
19+
align-items: center;
20+
margin-right: 0.875rem;
21+
color: ${palette.teal7};
22+
text-decoration: none;
23+
font-weight: 500;
24+
&:hover {
25+
background: ${palette.gray0};
26+
}
27+
`;
28+
29+
export interface PostTagsProps {
30+
tags: string[];
31+
}
32+
33+
const PostTags: React.FC<PostTagsProps> = ({ tags }) => {
34+
return (
35+
<PostTagsBlock>
36+
{tags.map(tag => (
37+
<Tag key={tag} to={`/tags/${tag}`}>
38+
{tag}
39+
</Tag>
40+
))}
41+
</PostTagsBlock>
42+
);
43+
};
44+
45+
export default PostTags;

src/components/post/__tests__/PostHead.test.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import { render } from 'react-testing-library';
33
import PostHead, { PostHeadProps } from '../PostHead';
4+
import { MemoryRouter } from 'react-router';
45

56
describe('PostHead', () => {
67
const setup = (props: Partial<PostHeadProps> = {}) => {
@@ -9,8 +10,15 @@ describe('PostHead', () => {
910
tags: ['tagA', 'tagB'],
1011
username: 'velopert',
1112
date: new Date(Date.now() - 1000 * 60 * 60 * 5).toString(),
13+
thumbnail:
14+
'https://images.velog.io/post-images/velopert/ac519a50-7732-11e9-bded-7fa91ac5b455/image.png',
15+
hideThumbnail: false,
1216
};
13-
const utils = render(<PostHead {...initialProps} {...props} />);
17+
const utils = render(
18+
<MemoryRouter>
19+
<PostHead {...initialProps} {...props} />
20+
</MemoryRouter>,
21+
);
1422
return {
1523
...utils,
1624
};
@@ -23,7 +31,27 @@ describe('PostHead', () => {
2331
const { getByText } = setup();
2432
getByText('velopert');
2533
});
26-
it('renders date', () => {});
34+
it('renders date', () => {
35+
const { getByText } = setup();
36+
getByText('약 5시간 전');
37+
});
38+
39+
it('renders tags', () => {
40+
const { getByText } = setup();
41+
getByText('tagA');
42+
getByText('tagB');
43+
});
44+
45+
it('renders thumbnail', () => {
46+
const { getByAltText } = setup();
47+
getByAltText('post-thumbnail');
48+
});
2749

28-
it('renders tags', () => {});
50+
it('hides thumbnail', () => {
51+
const { queryByAltText } = setup({
52+
hideThumbnail: true,
53+
});
54+
const thumbnail = queryByAltText('post-thumbnail');
55+
expect(thumbnail).toBeFalsy();
56+
});
2957
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as React from 'react';
2+
import { render } from 'react-testing-library';
3+
import PostTags, { PostTagsProps } from '../PostTags';
4+
import { MemoryRouter } from 'react-router-dom';
5+
6+
describe('PostTags', () => {
7+
const setup = (props: Partial<PostTagsProps> = {}) => {
8+
const initialProps: PostTagsProps = {
9+
tags: ['태그1', '태그2'],
10+
};
11+
const utils = render(
12+
<MemoryRouter>
13+
<PostTags {...initialProps} {...props} />
14+
</MemoryRouter>,
15+
);
16+
return {
17+
...utils,
18+
};
19+
};
20+
it('renders tags', () => {
21+
const { getByText } = setup();
22+
const tag1 = getByText('태그1');
23+
expect(tag1).toHaveAttribute('href', '/tags/태그1');
24+
getByText('태그2');
25+
});
26+
});

src/containers/post/PostViewer.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ const PostViewer: React.FC<PostViewerProps> = ({ username, urlSlug }) => {
3030
tags={post.tags}
3131
username={username}
3232
date={post.released_at}
33+
thumbnail={post.thumbnail}
34+
hideThumbnail={
35+
!!post.thumbnail && post.body.includes(post.thumbnail)
36+
}
3337
/>
3438
</>
3539
);

src/lib/graphql/post.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ export interface SinglePost {
4949
is_markdown: boolean;
5050
is_private: boolean;
5151
is_temp: boolean;
52+
thumbnail: string | null;
5253
user: {
54+
id: string;
5355
username: string;
5456
profile: {
5557
display_name: string;
@@ -97,6 +99,7 @@ export const READ_POST = gql`
9799
is_markdown
98100
is_private
99101
is_temp
102+
thumbnail
100103
user {
101104
id
102105
username

0 commit comments

Comments
 (0)