Skip to content

Commit 5e94bb0

Browse files
[Release] 2024-12-23
[Release] 2024-12-23
2 parents ccb54db + f1c0325 commit 5e94bb0

File tree

14 files changed

+178
-10
lines changed

14 files changed

+178
-10
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
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"

src/app/home/layout.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
}

src/app/home/page.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
import Container from "@/components/container";
21
import { getAllPosts } from "@/lib/api";
32
import Intro from "./_components/intro";
43
import HeroPost from "./_components/hero-post";
54
import 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
}

src/app/posts/[slug]/_components/post-alert/PostAlert.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff 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% 완벽하지 않습니다. 수정사항은{" "}

src/app/posts/[slug]/_components/post-body/PostBody.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff 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+
2231
export function PostBody({ content }: Props) {
2332
return (
2433
<div className="max-w-2xl mx-auto">
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import PostTOC from "./PostTOC";
2+
3+
export default PostTOC;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
};

src/app/posts/[slug]/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import Footer from "@/components/footer";
2+
import ScrollToTopButton from "@/components/scroll-to-top-button";
23
import { PropsWithChildren } from "react";
34

45
export default function PostLayout({ children }: PropsWithChildren) {
56
return (
67
<>
78
{children}
89
<Footer />
10+
<ScrollToTopButton />
911
</>
1012
);
1113
}

src/app/posts/[slug]/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Header from "@/components/header";
55
import PostHeader from "./_components/post-header";
66
import PostBody from "./_components/post-body";
77
import PostAlert from "./_components/post-alert";
8+
import PostTOC from "./_components/post-toc";
89

910
type 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>

0 commit comments

Comments
 (0)