File tree Expand file tree Collapse file tree 14 files changed +178
-10
lines changed
components/scroll-to-top-button Expand file tree Collapse file tree 14 files changed +178
-10
lines changed Original file line number Diff line number Diff line change 1616 "next" : " 15.0.2" ,
1717 "react" : " 19.0.0-rc-02c0e824-20241028" ,
1818 "react-dom" : " 19.0.0-rc-02c0e824-20241028" ,
19+ "react-icons" : " ^5.4.0" ,
1920 "remark" : " ^15.0.1" ,
2021 "remark-html" : " ^16.0.1" ,
2122 "tailwind-merge" : " ^2.5.5"
Original file line number Diff line number Diff line change 1+ import Container from "@/components/container" ;
2+ import ScrollToTopButton from "@/components/scroll-to-top-button" ;
3+ import { PropsWithChildren } from "react" ;
4+
5+ export default function Layout ( { children } : PropsWithChildren ) {
6+ return (
7+ < main >
8+ < Container > { children } </ Container >
9+ < ScrollToTopButton />
10+ </ main >
11+ ) ;
12+ }
Original file line number Diff line number Diff line change 1- import Container from "@/components/container" ;
21import { getAllPosts } from "@/lib/api" ;
32import Intro from "./_components/intro" ;
43import HeroPost from "./_components/hero-post" ;
54import MorePosts from "./_components/more-posts" ;
65
7- export default function Index ( ) {
6+ export default function Page ( ) {
87 const allPosts = getAllPosts ( ) ;
98
109 const [ heroPost , ...morePosts ] = allPosts ;
1110
1211 return (
13- < main >
14- < Container >
15- < Intro />
16- < HeroPost { ...heroPost } />
17- { morePosts . length > 0 && < MorePosts posts = { morePosts } /> }
18- </ Container >
19- </ main >
12+ < >
13+ < Intro />
14+ < HeroPost { ...heroPost } />
15+ { morePosts . length > 0 && < MorePosts posts = { morePosts } /> }
16+ </ >
2017 ) ;
2118}
Original file line number Diff line number Diff line change @@ -8,7 +8,7 @@ export default function PostAlert({ slug }: Props) {
88 const GITHUB_FILE_LINK = `https://github.com/Hong-JunHyeok/liam-tech-blog/blob/main/_posts/${ slug } .md` ;
99
1010 return (
11- < div className = "border-b bg-accent-1 border-accent-2 fixed top-0 w-full" >
11+ < div className = "border-b bg-accent-1 border-accent-2 fixed top-0 w-full z-50 " >
1212 < div className = "container mx-auto px-5" >
1313 < div className = "py-2 text-center text-sm " >
1414 지금 보시는 글은 100% 완벽하지 않습니다. 수정사항은{ " " }
Original file line number Diff line number Diff line change @@ -19,6 +19,15 @@ const marked = new Marked(
1919 } )
2020) ;
2121
22+ const renderer = new marked . Renderer ( ) ;
23+
24+ renderer . heading = ( { text, depth } ) => {
25+ const id = text ;
26+ return `<h${ depth } id="${ id } ">${ text } </h${ depth } >` ;
27+ } ;
28+
29+ marked . setOptions ( { renderer } ) ;
30+
2231export function PostBody ( { content } : Props ) {
2332 return (
2433 < div className = "max-w-2xl mx-auto" >
Original file line number Diff line number Diff line change 1+ "use client" ;
2+ import Link from "next/link" ;
3+ import { getHeadingList , moveToTitleTag } from "./utils" ;
4+ import classNames from "classnames" ;
5+
6+ type Props = {
7+ content : string ;
8+ } ;
9+
10+ function PostTOC ( { content } : Props ) {
11+ const headingList = getHeadingList ( content ) ;
12+
13+ const handleClickAnchor =
14+ ( title : string ) => ( event : React . MouseEvent < HTMLAnchorElement > ) => {
15+ event . preventDefault ( ) ;
16+ moveToTitleTag ( title ) ;
17+ } ;
18+
19+ return (
20+ < ul className = "max-w-2xl mx-auto flex-row" >
21+ { headingList . map ( ( { title, level } , index ) => {
22+ return (
23+ < li
24+ key = { index }
25+ className = { classNames (
26+ "mb-2" ,
27+ { "ml-6" : level === 2 } ,
28+ { "ml-12" : level === 3 } ,
29+ { "ml-18" : level === 4 }
30+ ) }
31+ >
32+ < Link
33+ href = { `#${ title } ` }
34+ onClick = { handleClickAnchor ( title ) }
35+ className = "text-slate-600 underline opacity-60 inline-block"
36+ >
37+ { title }
38+ </ Link >
39+ </ li >
40+ ) ;
41+ } ) }
42+ </ ul >
43+ ) ;
44+ }
45+
46+ export default PostTOC ;
Original file line number Diff line number Diff line change 1+ import PostTOC from "./PostTOC" ;
2+
3+ export default PostTOC ;
Original file line number Diff line number Diff line change 1+ export const getHeadingWithLevel = ( heading : string ) => {
2+ const headingPattern = / ^ ( # + ) \s + ( .* ) $ / ;
3+
4+ const match = heading . match ( headingPattern ) ;
5+ if ( ! match ) return { level : 0 , title : "" } ;
6+
7+ const level = match [ 1 ] . length ;
8+ const title = match [ 2 ] . trim ( ) ;
9+
10+ return { level, title } ;
11+ } ;
12+
13+ export const getHeadingList = ( content : string ) => {
14+ const headingPattern = / ^ ( # + ) \s + ( .* ) $ / gm;
15+ const matchedHeadings = content . match ( headingPattern ) ;
16+
17+ if ( ! matchedHeadings ) return [ ] ;
18+
19+ const headingsWithLevel = matchedHeadings . map ( getHeadingWithLevel ) ;
20+
21+ const minLevel = Math . min ( ...headingsWithLevel . map ( ( { level } ) => level ) ) ;
22+
23+ return headingsWithLevel . map ( ( { level, title } ) => ( {
24+ level : level - minLevel + 1 ,
25+ title,
26+ } ) ) ;
27+ } ;
28+
29+ export const moveToTitleTag = ( targetId : string ) => {
30+ const targetElement = document . getElementById ( targetId ) ;
31+ if ( ! targetElement ) return ;
32+ const position = targetElement . getBoundingClientRect ( ) ;
33+ window . scrollTo ( {
34+ behavior : "smooth" ,
35+ left : position . left ,
36+ top : position . top + window . scrollY - 60 ,
37+ } ) ;
38+ } ;
Original file line number Diff line number Diff line change 11import Footer from "@/components/footer" ;
2+ import ScrollToTopButton from "@/components/scroll-to-top-button" ;
23import { PropsWithChildren } from "react" ;
34
45export default function PostLayout ( { children } : PropsWithChildren ) {
56 return (
67 < >
78 { children }
89 < Footer />
10+ < ScrollToTopButton />
911 </ >
1012 ) ;
1113}
Original file line number Diff line number Diff line change @@ -5,6 +5,7 @@ import Header from "@/components/header";
55import PostHeader from "./_components/post-header" ;
66import PostBody from "./_components/post-body" ;
77import PostAlert from "./_components/post-alert" ;
8+ import PostTOC from "./_components/post-toc" ;
89
910type Params = {
1011 params : Promise < {
@@ -27,6 +28,7 @@ export default async function Post(props: Params) {
2728 < Header />
2829 < article className = "mb-32" >
2930 < PostHeader { ...post } />
31+ < PostTOC content = { post . content } />
3032 < PostBody content = { post . content } />
3133 </ article >
3234 </ Container >
You can’t perform that action at this time.
0 commit comments