dh_demo

DreamHanks demo project
git clone git://git.lair.cx/dh_demo
Log | Files | Refs | README

commit b6e43180cd82ba656e2ad0bd0a31151c9f04d06e
parent 16a82ae2a84f72372b54515472ce2ed0db9413e1
Author: Yongbin Kim <iam@yongbin.kim>
Date:   Thu, 26 Jan 2023 10:38:23 +0900

feat: ThreadList 컴포넌트 추가

Signed-off-by: Yongbin Kim <iam@yongbin.kim>

Diffstat:
Acomponents/threads/ThreadList.module.css | 1+
Acomponents/threads/ThreadList.module.css.map | 2++
Acomponents/threads/ThreadList.module.scss | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acomponents/threads/ThreadList.tsx | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/models/thread.ts | 15+++++++++++++--
Mlib/models/wiki_talk.ts | 14++++++++++++--
Mpages/talk/[slug]/[...path].tsx | 3++-
7 files changed, 144 insertions(+), 5 deletions(-)

diff --git a/components/threads/ThreadList.module.css b/components/threads/ThreadList.module.css @@ -0,0 +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 */ diff --git a/components/threads/ThreadList.module.css.map b/components/threads/ThreadList.module.css.map @@ -0,0 +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 diff --git a/components/threads/ThreadList.module.scss b/components/threads/ThreadList.module.scss @@ -0,0 +1,59 @@ +@use 'core/vars'; +@use 'core/typography'; +@use 'core/colors'; +@use 'core/elevate'; + +.threads { + display: flex; + flex-direction: column; + width: 100%; + gap: vars.$gap; +} + +.thread { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + padding: 0.5rem; + cursor: pointer; + user-select: none; + + a { + color: inherit; + font-weight: bold; + } + + &:hover { + @include colors.apply-themes() using ($theme) { + @include elevate.apply-background-color($theme, 2, 'on-surface'); + @include elevate.apply-box-shadow(2); + } + } + + @include colors.apply-themes() using ($theme) { + border: 1px solid colors.get($theme, 'outline'); + background: colors.get($theme, 'background'); + border-radius: 0.5rem; + } + + .title { + display: block; + padding: 0.5rem; + @include typography.apply('title-large'); + } + + .meta { + padding: 0.25rem 0.5rem; + @include typography.apply('body-small'); + + a:hover { + text-decoration: underline; + } + } + + .preview { + padding: 0.25rem 0.5rem; + @include typography.apply('body-medium'); + } +} diff --git a/components/threads/ThreadList.tsx b/components/threads/ThreadList.tsx @@ -0,0 +1,55 @@ +import { Thread } from '@/lib/models/thread' +import Link from 'next/link' +import { useRouter } from 'next/router' +import styles from './ThreadList.module.css' +import { MouseEvent } from 'react' + +export interface ThreadListProps { + threads?: Thread[] +} + +export default function ThreadList (props: ThreadListProps) { + const router = useRouter() + + const stopPropagation = (e: MouseEvent) => { + e.stopPropagation() + } + + const handleThreadClicked = (threadId: number) => { + return (e: MouseEvent<HTMLElement>) => { + e.preventDefault() + router.push( + '/threads/[...path]', + `/threads/${threadId}`, + ).catch((e) => { + console.error(e) + }) + } + } + + return ( + <div className={styles['threads']}> + {props.threads?.map((thread) => ( + <div + key={thread.id} + className={styles.thread} + onClick={handleThreadClicked(thread.id)} + > + <div className={styles.meta}> + Posted by&nbsp; + <Link + href={`/users/${thread.authorId}`} + onClick={stopPropagation} + > + {thread.authorName} + </Link> + </div> + <div className={styles.title}>{thread.title}</div> + {thread.preview && ( + <div className={styles.preview}>{thread.preview}</div> + )} + </div> + ))} + </div> + ) +} diff --git a/lib/models/thread.ts b/lib/models/thread.ts @@ -6,11 +6,21 @@ export interface Thread { authorId: number authorName: string title: string + preview?: string createdAt: Date } const SQL_LIST_THREADS = ` - select t.id, t.author_id, u.nickname, t.title, t.created_at + select t.id, + t.author_id, + u.nickname, + t.title, + (select content + from thread_comments + where thread_id = t.id + order by id + limit 1) as preview, + t.created_at from threads t left join user_profiles u on u.login_id = t.author_id order by t.created_at desc @@ -30,7 +40,8 @@ export const listThreads = modelBehaviour< authorId: row[1], authorName: row[2], title: row[3], - createdAt: row[4], + preview: row[4], + createdAt: row[5], })) }) diff --git a/lib/models/wiki_talk.ts b/lib/models/wiki_talk.ts @@ -3,7 +3,16 @@ import { createThread, Thread } from '@/lib/models/thread' import { OkPacket, RowDataPacket } from 'mysql2' const SQL_LIST_WIKI_TALKS = ` - select t.id, t.author_id, u.nickname, t.title, t.created_at + select t.id, + t.author_id, + u.nickname, + t.title, + (select content + from thread_comments + where thread_id = t.id + order by id + limit 1) as preview, + t.created_at from threads t inner join wiki_talks wt on t.id = wt.thread_id left join user_profiles u on u.login_id = t.author_id @@ -25,7 +34,8 @@ export const listWikiTalks = modelBehaviour< authorId: row[1], authorName: row[2], title: row[3], - createdAt: row[4], + preview: row[4], + createdAt: row[5], })) }) diff --git a/pages/talk/[slug]/[...path].tsx b/pages/talk/[slug]/[...path].tsx @@ -7,6 +7,7 @@ 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 ThreadList from '@/components/threads/ThreadList' import { ApiError } from '@/lib/apierror' import { Thread } from '@/lib/models/thread' import { getWikiAndPageACLViaPath } from '@/lib/models/wiki_page' @@ -64,7 +65,7 @@ export default function TestPage (props: TestPageProps) { {props.talks.length === 0 ? ( <div>No talks</div> ) : ( - <div>asdf</div> + <ThreadList threads={props.talks} /> )} </Section>