commit 4f727a4e81dba277bad41be200ee47051a510e4d
parent 627368401c9d0ec4c0cec035311a1e936424b8cd
Author: Yongbin Kim <iam@yongbin.kim>
Date: Fri, 27 Jan 2023 09:36:32 +0900
feat: Thread List에 Pagination 추가
Signed-off-by: Yongbin Kim <iam@yongbin.kim>
Diffstat:
5 files changed, 59 insertions(+), 10 deletions(-)
diff --git a/components/threads/ThreadList.module.css b/components/threads/ThreadList.module.css
@@ -1 +1 @@
-.threads{display:flex;flex-direction:column;width:100%;gap:1rem}.thread{display:flex;flex-direction:column;align-items:flex-start;width:100%;padding:.5rem;cursor:pointer;user-select:none;border:1px solid #6f7975;background:#fafdfa;border-radius:.5rem}.thread a{color:inherit;font-weight:bold}.thread:hover{background-color:#d8dbd9;box-shadow:0px 1px 2px 0px rgba(0, 0, 0, 0.2),0px 2px 6px 2px rgba(0, 0, 0, 0.1)}@media(prefers-color-scheme: dark){.thread:hover{background-color:#373a39;box-shadow:0px 1px 2px 0px rgba(0, 0, 0, 0.2),0px 2px 6px 2px rgba(0, 0, 0, 0.1)}}@media(prefers-color-scheme: dark){.thread{border:1px solid #89938f;background:#191c1b;border-radius:.5rem}}.thread .title{display:block;padding:.5rem;line-height:1.75rem;font-size:1.375rem;letter-spacing:0rem;font-weight:400}.thread .meta{padding:.25rem .5rem;line-height:1rem;font-size:.75rem;letter-spacing:.0333333333rem;font-weight:400}.thread .meta a:hover{text-decoration:underline}.thread .preview{padding:.25rem .5rem;line-height:1.25rem;font-size:.875rem;letter-spacing:.0178571429rem;font-weight:400}/*# sourceMappingURL=ThreadList.module.css.map */
+.threads{display:flex;flex-direction:column;width:100%;gap:1rem;margin-bottom:1rem}.thread{display:flex;flex-direction:column;align-items:flex-start;width:100%;padding:.5rem;cursor:pointer;user-select:none;border:1px solid #6f7975;background:#fafdfa;border-radius:.5rem}.thread a{color:inherit;font-weight:bold}.thread:hover{background-color:#d8dbd9;box-shadow:0px 1px 2px 0px rgba(0, 0, 0, 0.2),0px 2px 6px 2px rgba(0, 0, 0, 0.1)}@media(prefers-color-scheme: dark){.thread:hover{background-color:#373a39;box-shadow:0px 1px 2px 0px rgba(0, 0, 0, 0.2),0px 2px 6px 2px rgba(0, 0, 0, 0.1)}}@media(prefers-color-scheme: dark){.thread{border:1px solid #89938f;background:#191c1b;border-radius:.5rem}}.thread .title{display:block;padding:.5rem;line-height:1.75rem;font-size:1.375rem;letter-spacing:0rem;font-weight:400}.thread .meta{padding:.25rem .5rem;line-height:1rem;font-size:.75rem;letter-spacing:.0333333333rem;font-weight:400}.thread .meta a:hover{text-decoration:underline}.thread .preview{padding:.25rem .5rem;line-height:1.25rem;font-size:.875rem;letter-spacing:.0178571429rem;font-weight:400}/*# sourceMappingURL=ThreadList.module.css.map */
diff --git a/components/threads/ThreadList.module.css.map b/components/threads/ThreadList.module.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["ThreadList.module.scss","../../styles/core/_vars.scss","../../styles/core/_elevate.scss","../../styles/core/_colors.scss","../../styles/core/_typography.scss"],"names":[],"mappings":"AAKA,SACE,aACA,sBACA,WACA,ICTI,KDYN,QACE,aACA,sBACA,uBACA,WACA,cACA,eACA,iBAeE,yBACA,mBACA,oBAfF,UACE,cACA,iBAGF,cE0BA,yBAUA,iFCkEA,mCHtGA,cE0BA,yBAUA,kFCkEA,mCHpHF,QAsBI,yBACA,mBACA,qBAGF,eACE,cACA,cI6EA,oBACA,mBACA,oBACA,gBJ5EF,cACE,qBIwEA,iBACA,iBACA,8BACA,gBJxEA,sBACE,0BAIJ,iBACE,qBI+DA,oBACA,kBACA,8BACA","file":"ThreadList.module.css"}
-\ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["ThreadList.module.scss","../../styles/core/_vars.scss","../../styles/core/_elevate.scss","../../styles/core/_colors.scss","../../styles/core/_typography.scss"],"names":[],"mappings":"AAKA,SACE,aACA,sBACA,WACA,ICTI,KDUJ,cCVI,KDaN,QACE,aACA,sBACA,uBACA,WACA,cACA,eACA,iBAeE,yBACA,mBACA,oBAfF,UACE,cACA,iBAGF,cEyBA,yBAUA,iFCkEA,mCHrGA,cEyBA,yBAUA,kFCkEA,mCHnHF,QAsBI,yBACA,mBACA,qBAGF,eACE,cACA,cI4EA,oBACA,mBACA,oBACA,gBJ3EF,cACE,qBIuEA,iBACA,iBACA,8BACA,gBJvEA,sBACE,0BAIJ,iBACE,qBI8DA,oBACA,kBACA,8BACA","file":"ThreadList.module.css"}
+\ No newline at end of file
diff --git a/components/threads/ThreadList.module.scss b/components/threads/ThreadList.module.scss
@@ -8,6 +8,7 @@
flex-direction: column;
width: 100%;
gap: vars.$gap;
+ margin-bottom: vars.$gap;
}
.thread {
diff --git a/lib/models/wiki_talk.ts b/lib/models/wiki_talk.ts
@@ -2,6 +2,23 @@ import { modelBehaviour } from '@/lib/model_helpers'
import { createThread, Thread } from '@/lib/models/thread'
import { OkPacket, RowDataPacket } from 'mysql2'
+const SQL_COUNT_WIKI_TALKS = `
+ select count(*)
+ from wiki_talks
+ where page_id = ?
+`
+
+export const countWikiTalks = modelBehaviour<
+ [pageId: number],
+ number
+>(async (conn, args) => {
+ const [rows] = await conn.query<RowDataPacket[]>({
+ sql: SQL_COUNT_WIKI_TALKS,
+ }, args)
+
+ return rows[0][0]
+})
+
const SQL_LIST_WIKI_TALKS = `
select t.id,
t.author_id,
diff --git a/pages/talk/[slug]/[...path].tsx b/pages/talk/[slug]/[...path].tsx
@@ -7,22 +7,28 @@ import Container from '@/components/layout/Container'
import Divider from '@/components/layout/Divider'
import Hero from '@/components/layout/Hero'
import Section from '@/components/layout/Section'
+import { Pagination } from '@/components/Pagination'
import ThreadList from '@/components/threads/ThreadList'
import { ApiError } from '@/lib/apierror'
+import { withConnection } from '@/lib/model_helpers'
import { Thread } from '@/lib/models/thread'
import { getWikiAndPageACLViaPath } from '@/lib/models/wiki_page'
-import { listWikiTalks } from '@/lib/models/wiki_talk'
+import { countWikiTalks, listWikiTalks } from '@/lib/models/wiki_talk'
+import { parseIntOrDefault } from '@/lib/utils/number'
import { CreateTalkResponse } from '@/pages/api/talks/[slug]/[...path]'
import { GetServerSideProps } from 'next'
-import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
+const PAGE_SIZE = 10
+
export interface TestPageProps {
wikiSlug: string
pageId: number
pagePath: string
talks: Thread[]
+ counts: number
+ page: number
}
export const getServerSideProps: GetServerSideProps<TestPageProps> = async (context) => {
@@ -35,21 +41,39 @@ export const getServerSideProps: GetServerSideProps<TestPageProps> = async (cont
const path = paths.join('/')
- const pageACLInfo = await getWikiAndPageACLViaPath([slug, path])
- if (pageACLInfo === null) {
+ const page = Math.max(1, parseIntOrDefault(context.query.page, 1))
+
+ const result = await withConnection(async (conn): Promise<[
+ aclInfo: Exclude<Awaited<ReturnType<typeof getWikiAndPageACLViaPath>>, null>,
+ talks: Awaited<ReturnType<typeof listWikiTalks>>,
+ counts: number,
+ ] | null> => {
+ const aclInfo = await getWikiAndPageACLViaPath(conn, [slug, path])
+ if (aclInfo === null) {
+ return null
+ }
+
+ const talks = await listWikiTalks(conn, [aclInfo.pageId, PAGE_SIZE, (page - 1) * PAGE_SIZE])
+ const counts = await countWikiTalks(conn, [aclInfo.pageId])
+
+ return [aclInfo, talks, counts]
+ })
+ if (result === null) {
return {
notFound: true,
}
}
- const talks = await listWikiTalks([pageACLInfo.pageId, 10, 0])
+ const [aclInfo, talks, counts] = result
return {
props: {
wikiSlug: slug,
- pageId: pageACLInfo.pageId,
+ pageId: aclInfo.pageId,
pagePath: path,
talks: talks,
+ counts: counts,
+ page: page,
},
}
}
@@ -65,7 +89,14 @@ export default function TestPage (props: TestPageProps) {
{props.talks.length === 0 ? (
<div>No talks</div>
) : (
- <ThreadList threads={props.talks} />
+ <>
+ <ThreadList threads={props.talks}/>
+ <Pagination
+ page={props.page}
+ totalCount={props.counts}
+ pageSize={PAGE_SIZE}
+ />
+ </>
)}
</Section>