Skip to content

Commit 70701a4

Browse files
committed
ページコンポーネント実装完了
1 parent 85ec059 commit 70701a4

File tree

26 files changed

+662
-66
lines changed

26 files changed

+662
-66
lines changed

aspida.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1+
// eslint-disable-next-line @typescript-eslint/no-var-requires
2+
require('dotenv').config({ path: './.env.local' })
3+
14
module.exports = { input: 'src/api', baseURL: `https://${process.env.SERVICE_ID}.microcms.io/api/v1` }

lib/$path.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
/* eslint-disable */
22
export const pagesPath = {
3+
$404: {
4+
$url: (url?: { hash?: string }) => ({ pathname: '/404' as const, hash: url?.hash })
5+
},
6+
_slug: (slug: string | number) => ({
7+
$url: (url?: { hash?: string }) => ({ pathname: '/[slug]' as const, query: { slug }, hash: url?.hash })
8+
}),
9+
category: {
10+
_categoryId: (categoryId: string | number) => ({
11+
page: {
12+
_pageNumber: (pageNumber: string | number) => ({
13+
$url: (url?: { hash?: string }) => ({ pathname: '/category/[categoryId]/page/[pageNumber]' as const, query: { categoryId, pageNumber }, hash: url?.hash })
14+
})
15+
}
16+
})
17+
},
18+
page: {
19+
_pageNumber: (pageNumber: string | number) => ({
20+
$url: (url?: { hash?: string }) => ({ pathname: '/page/[pageNumber]' as const, query: { pageNumber }, hash: url?.hash })
21+
})
22+
},
23+
search: {
24+
$url: (url?: { hash?: string }) => ({ pathname: '/search' as const, hash: url?.hash })
25+
},
326
$url: (url?: { hash?: string }) => ({ pathname: '/' as const, hash: url?.hash })
427
}
528

package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"dev:path": "pathpida --watch",
1010
"dev:api": "aspida --watch",
1111
"build": "pathpida && aspida && next build",
12-
"generate": "yarn build && next export",
1312
"start": "next start",
1413
"test": "jest",
1514
"fix": "prettier --write './**/*.{js,jsx,ts,tsx}'",
@@ -32,24 +31,29 @@
3231
"@aspida/axios": "^1.6.3",
3332
"axios": "^0.21.1",
3433
"dayjs": "^1.10.4",
34+
"highlight.js": "^10.7.2",
35+
"jsdom": "^16.5.3",
3536
"next": "^10.0.6",
3637
"react": "^17.0.1",
3738
"react-dom": "^17.0.1",
3839
"react-scroll": "^1.8.2",
39-
"react-use": "^17.2.1"
40+
"react-use": "^17.2.4",
41+
"swr": "^0.5.6"
4042
},
4143
"devDependencies": {
4244
"@babel/core": "^7.12.10",
4345
"@next/bundle-analyzer": "^10.1.1",
4446
"@testing-library/jest-dom": "^5.11.10",
4547
"@testing-library/react": "^11.2.5",
4648
"@testing-library/react-hooks": "^5.1.1",
49+
"@types/jsdom": "^16.2.10",
4750
"@types/node": "^14.14.37",
4851
"@types/react": "^17.0.3",
4952
"@types/react-scroll": "^1.8.2",
5053
"@typescript-eslint/eslint-plugin": "^4.20.0",
5154
"@typescript-eslint/parser": "^4.20.0",
5255
"babel-jest": "^26.6.3",
56+
"dotenv": "^9.0.0",
5357
"eslint": "^7.23.0",
5458
"eslint-config-prettier": "^7.2.0",
5559
"eslint-plugin-import": "^2.22.1",
@@ -70,6 +74,8 @@
7074
"next-optimized-images": "^2.6.2",
7175
"npm-run-all": "^4.1.5",
7276
"pathpida": "^0.14.0",
77+
"postcss-custom-properties": "^11.0.0",
78+
"postcss-nested": "^5.0.5",
7379
"prettier": "^2.2.1",
7480
"react-test-renderer": "^17.0.2",
7581
"responsive-loader": "^2.3.0",

pages/404.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { NextPage } from 'next'
2+
3+
import styles from '~/src/styles/pages/404.module.css'
4+
5+
const ErrorPage: NextPage = () => {
6+
return (
7+
<>
8+
<div className={styles.container}>
9+
<dl>
10+
<dt className={styles.status}>404</dt>
11+
<dd className={styles.message}>ページが見つかりません</dd>
12+
</dl>
13+
</div>
14+
</>
15+
)
16+
}
17+
18+
export default ErrorPage

pages/[slug].tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import hljs from 'highlight.js'
2+
import { JSDOM } from 'jsdom'
3+
import type { GetStaticPaths, GetStaticPathsResult, GetStaticProps, NextPage } from 'next'
4+
5+
import { BlogDetailLayout } from '~/src/components/BlogDetailLayout'
6+
import { BlogDetailLayoutProps } from '~/src/components/BlogDetailLayout/BlogDetailLayout'
7+
import { apiClient } from '~/src/utils/apiClient'
8+
import { getContents } from '~/src/utils/getContents'
9+
import { headers } from '~/src/utils/microCMSHeaders'
10+
11+
export const getStaticPaths: GetStaticPaths = async () => {
12+
const { contents } = await getContents()
13+
const paths: GetStaticPathsResult['paths'] = contents.map((content) => ({
14+
params: {
15+
slug: content.id,
16+
},
17+
}))
18+
19+
return {
20+
paths,
21+
fallback: 'blocking',
22+
}
23+
}
24+
25+
const processingDom = (htmlString: string) => {
26+
const dom = new JSDOM(htmlString)
27+
const toc: BlogDetailLayoutProps['toc'] = []
28+
dom.window.document.querySelectorAll('h1, h2, h3').forEach((heading) => {
29+
toc.push({
30+
id: heading.id,
31+
name: heading.tagName,
32+
text: heading.textContent ?? '',
33+
})
34+
})
35+
dom.window.document.querySelectorAll('pre code').forEach((element) => {
36+
const res = hljs.highlightAuto(element.textContent ?? '')
37+
element.innerHTML = res.value
38+
element.classList.add('hljs')
39+
})
40+
dom.window.document.querySelectorAll('img').forEach((element) => {
41+
element.classList.add('lazyload')
42+
element.setAttribute('data-src', element.src)
43+
element.src = ''
44+
})
45+
46+
return { toc }
47+
}
48+
49+
type Props = BlogDetailLayoutProps
50+
51+
export const getStaticProps: GetStaticProps<Props> = async ({ params, preview, previewData }) => {
52+
if (params === undefined || typeof params.slug !== 'string')
53+
throw Error('pagesのディレクトリ構造かファイル名が間違っています。')
54+
55+
const contents = await getContents()
56+
const content = await apiClient.blog._slug(params.slug).$get({
57+
headers,
58+
query: {
59+
depth: 2,
60+
draftKey: preview ? previewData.draftKey : undefined,
61+
},
62+
})
63+
const { toc } = processingDom(content.body)
64+
65+
return {
66+
props: { ...contents, content, toc, latestArticles: contents.contents },
67+
}
68+
}
69+
70+
const IndexPage: NextPage<Props> = (props) => {
71+
return <BlogDetailLayout {...props} />
72+
}
73+
74+
export default IndexPage

pages/_document.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ class MyDocument extends Document {
2727
<body>
2828
<Main />
2929
<NextScript />
30+
<script
31+
src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js"
32+
integrity="sha512-q583ppKrCRc7N5O0n2nzUiJ+suUv7Et1JGels4bXOaMFQcamPk9HjdUknZuuFjBNs7tsMuadge5k9RzdmO+1GQ=="
33+
crossOrigin="anonymous"
34+
async
35+
></script>
3036
</body>
3137
</Html>
3238
)

pages/api/preview.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { NextApiRequest, NextApiResponse } from 'next'
2+
3+
import { apiClient } from '~/src/utils/apiClient'
4+
import { headers } from '~/src/utils/microCMSHeaders'
5+
6+
export default async (req: NextApiRequest, res: NextApiResponse) => {
7+
const { draftKey, slug } = req.query
8+
if (typeof draftKey !== 'string' || typeof slug !== 'string') {
9+
res.status(404).end()
10+
return
11+
}
12+
13+
const data = await apiClient.blog._slug(slug).$get({ headers, query: { fields: 'id', draftKey } })
14+
15+
if (!data) {
16+
return res.status(401).json({ message: 'Invalid slug' })
17+
}
18+
19+
res.setPreviewData({ draftKey })
20+
res.writeHead(307, { Location: `/${slug}` })
21+
res.end('Preview mode enabled')
22+
}

pages/api/search.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { NextApiRequest, NextApiResponse } from 'next'
2+
3+
import { Methods } from '~/src/api/blog'
4+
import { apiClient } from '~/src/utils/apiClient'
5+
import { headers } from '~/src/utils/microCMSHeaders'
6+
7+
type Data = Methods['get']['resBody']
8+
9+
export default async (req: NextApiRequest, res: NextApiResponse<Data>) => {
10+
if (typeof req.query.q !== 'string') {
11+
res.status(404).end()
12+
return
13+
}
14+
15+
const data = await apiClient.blog.$get({
16+
headers,
17+
query: {
18+
q: req.query.q,
19+
},
20+
})
21+
22+
res.status(200).json({ ...data })
23+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { GetStaticPaths, GetStaticProps, NextPage } from 'next'
2+
3+
import BlogListLayout, { BlogListLayoutProps } from '~/src/components/BlogListLayout/BlogListLayout'
4+
import { apiClient } from '~/src/utils/apiClient'
5+
import { getContents, limit } from '~/src/utils/getContents'
6+
import { headers } from '~/src/utils/microCMSHeaders'
7+
8+
export const getStaticPaths: GetStaticPaths = async () => {
9+
const { contents: categories } = await apiClient.categories.$get({ headers })
10+
const [paths] = await Promise.all(
11+
categories.map((category) =>
12+
apiClient.blog
13+
.$get({ headers, query: { filters: `category[equals]${category.id}`, limit: 1 } })
14+
.then(({ totalCount }) => {
15+
return [...Array(Math.ceil(totalCount / limit)).keys()].map((num) => ({
16+
params: {
17+
categoryId: category.id,
18+
pageNumber: (num + 1).toString(),
19+
},
20+
}))
21+
})
22+
)
23+
)
24+
25+
return {
26+
paths,
27+
fallback: false,
28+
}
29+
}
30+
31+
type Props = BlogListLayoutProps
32+
33+
export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
34+
if (params === undefined || typeof params.pageNumber !== 'string' || typeof params.categoryId !== 'string')
35+
throw Error('pagesの、ディレクトリ構造かファイル名が間違っています。')
36+
37+
const contents = await getContents(Number(params.pageNumber), params.categoryId)
38+
39+
return {
40+
props: { ...contents },
41+
}
42+
}
43+
44+
const PagingPage: NextPage<Props> = (props) => {
45+
return <BlogListLayout {...props} />
46+
}
47+
48+
export default PagingPage

pages/index.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
import type { NextPage } from 'next'
1+
import type { GetStaticProps, NextPage } from 'next'
22

3-
const IndexPage: NextPage = () => {
4-
return <></>
3+
import BlogListLayout, { BlogListLayoutProps } from '~/src/components/BlogListLayout/BlogListLayout'
4+
import { getContents } from '~/src/utils/getContents'
5+
6+
type Props = BlogListLayoutProps
7+
8+
export const getStaticProps: GetStaticProps<Props> = async () => {
9+
const contents = await getContents()
10+
11+
return {
12+
props: { ...contents },
13+
}
14+
}
15+
16+
const IndexPage: NextPage<Props> = (props) => {
17+
return <BlogListLayout {...props} />
518
}
619

720
export default IndexPage

0 commit comments

Comments
 (0)