Skip to content

Commit 94e6abb

Browse files
committed
feat: VitePress 기본 설정 및 커스텀 테마 구성
- VitePress config.mts 설정 추가 - 커스텀 테마 및 스타일 설정 - Vue 컴포넌트 타입 정의 추가 - 기본 레이아웃 및 스타일 구성
1 parent 5669a87 commit 94e6abb

8 files changed

Lines changed: 414 additions & 0 deletions

File tree

.vitepress/config.mts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { defineConfig } from "vitepress";
2+
import { generateSidebar } from "./plugins/sidebar";
3+
4+
import { createAutoGeneratePostsPlugin } from "./plugins/posts";
5+
6+
// https://vitepress.dev/reference/site-config
7+
export default defineConfig({
8+
title: "🦷 ToothlessDev",
9+
description: "A VitePress Site",
10+
themeConfig: {
11+
// https://vitepress.dev/reference/default-theme-config
12+
nav: [
13+
{ text: "Home", link: "/" },
14+
{
15+
text: "Blog",
16+
items: [
17+
{ text: "전체", link: "/pages/posts/" },
18+
{ text: "JavaScript", link: "/pages/posts/javascript/" },
19+
{ text: "React", link: "/pages/posts/react/" },
20+
{ text: "Vue.js", link: "/pages/posts/vue/" },
21+
{ text: "백엔드", link: "/pages/posts/backend/" },
22+
{ text: "WEB", link: "/pages/posts/web/" },
23+
],
24+
},
25+
],
26+
27+
sidebar: {
28+
"/pages/posts/": generateSidebar(),
29+
30+
"/": [
31+
{
32+
text: "Examples",
33+
items: [
34+
{ text: "Markdown Examples", link: "/markdown-examples" },
35+
{ text: "Runtime API Examples", link: "/api-examples" },
36+
],
37+
},
38+
],
39+
},
40+
41+
socialLinks: [{ icon: "github", link: "https://github.com/toothlessdev" }],
42+
},
43+
44+
sitemap: {
45+
hostname: "https://toothlessdev.github.io/",
46+
},
47+
48+
vite: {
49+
plugins: [createAutoGeneratePostsPlugin()],
50+
},
51+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
.blog-list {
2+
margin: 2rem 0;
3+
}
4+
5+
.blog-cards {
6+
display: grid;
7+
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
8+
gap: 1.5rem;
9+
margin-top: 2rem;
10+
}
11+
12+
.blog-card {
13+
background: var(--vp-c-bg-soft);
14+
border: 1px solid var(--vp-c-border);
15+
border-radius: 12px;
16+
padding: 1rem;
17+
transition: all 0.3s ease;
18+
height: fit-content;
19+
cursor: pointer;
20+
}
21+
22+
.blog-card:hover {
23+
border-color: var(--vp-c-brand-1);
24+
box-shadow: 0 4px 12px var(--vp-c-shadow-2);
25+
transform: translateY(-2px);
26+
}
27+
28+
.blog-cards .blog-card .blog-card-footer {
29+
display: flex;
30+
justify-content: space-between;
31+
align-items: center;
32+
margin-top: 1rem;
33+
padding-top: 0.75rem;
34+
border-top: 1px solid var(--vp-c-divider);
35+
}
36+
37+
.blog-cards .blog-card .blog-card-meta {
38+
display: flex;
39+
align-items: center;
40+
gap: 0.5rem;
41+
}
42+
43+
.blog-cards .blog-card .blog-card-category-indicator {
44+
width: 12px;
45+
height: 12px;
46+
border-radius: 50%;
47+
display: inline-block;
48+
}
49+
50+
.blog-cards .blog-card .blog-card-category-name {
51+
color: var(--vp-c-text-2);
52+
font-size: 0.8rem;
53+
font-weight: 500;
54+
}
55+
56+
.blog-cards .blog-card .blog-card-date {
57+
color: var(--vp-c-text-2);
58+
font-size: 0.8rem;
59+
}
60+
61+
.blog-cards .blog-card .blog-card-title.blog-card-title {
62+
margin-top: 0;
63+
margin-bottom: 0.5rem;
64+
font-size: 1.1rem;
65+
font-weight: 600;
66+
line-height: 1.4;
67+
color: var(--vp-c-text-1);
68+
transition: color 0.2s ease;
69+
}
70+
71+
.blog-card:hover .blog-card-title.blog-card-title {
72+
color: var(--vp-c-brand-1);
73+
}
74+
75+
.blog-cards .blog-card .blog-card-description {
76+
color: var(--vp-c-text-2);
77+
line-height: 1.5;
78+
margin-bottom: 0;
79+
margin-top: 0;
80+
font-size: 0.9rem;
81+
display: -webkit-box;
82+
-webkit-line-clamp: 2;
83+
line-clamp: 2;
84+
-webkit-box-orient: vertical;
85+
overflow: hidden;
86+
text-overflow: ellipsis;
87+
}
88+
89+
@media (max-width: 768px) {
90+
.blog-cards {
91+
grid-template-columns: 1fr;
92+
gap: 1rem;
93+
}
94+
95+
.blog-card {
96+
padding: 1rem;
97+
}
98+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<template>
2+
<div class="blog-list">
3+
<div class="blog-cards">
4+
<article
5+
v-for="post in sortedPosts"
6+
:key="post.url"
7+
class="blog-card"
8+
@click="navigateToPost(post.url)"
9+
>
10+
<div class="blog-card-content">
11+
<h3 class="blog-card-title">
12+
{{ post.frontmatter.title }}
13+
</h3>
14+
15+
<p class="blog-card-description">
16+
{{ post.frontmatter.description }}
17+
</p>
18+
19+
<div class="blog-card-footer">
20+
<div class="blog-card-meta">
21+
<span
22+
class="blog-card-category-indicator"
23+
:style="{
24+
backgroundColor: getCategoryColor(post.frontmatter.category),
25+
}"
26+
></span>
27+
<span class="blog-card-category-name">{{
28+
post.frontmatter.category
29+
}}</span>
30+
</div>
31+
<time class="blog-card-date">
32+
{{ formatDate(post.frontmatter.createdAt) }}
33+
</time>
34+
</div>
35+
</div>
36+
</article>
37+
</div>
38+
</div>
39+
</template>
40+
41+
<script setup lang="ts">
42+
import { computed } from "vue";
43+
import { posts } from "../../data/posts";
44+
import { getCategoryColor } from "../constants/colors";
45+
import "./BlogList.css";
46+
47+
interface Props {
48+
category?: string;
49+
}
50+
51+
const props = withDefaults(defineProps<Props>(), {
52+
category: undefined,
53+
});
54+
55+
const sortedPosts = computed(() => {
56+
if (props.category) {
57+
return posts.filter(
58+
(post) => post.frontmatter.category.toLowerCase() === props.category!.toLowerCase(),
59+
);
60+
}
61+
return posts;
62+
});
63+
64+
// 날짜 포맷팅 함수 (yyyy-mm-dd 형식)
65+
function formatDate(dateString: string) {
66+
const date = new Date(dateString);
67+
return date
68+
.toLocaleDateString("ko-KR", {
69+
year: "numeric",
70+
month: "2-digit",
71+
day: "2-digit",
72+
})
73+
.replace(/\. /g, "-")
74+
.replace(/\.$/, "");
75+
}
76+
77+
function navigateToPost(url: string) {
78+
window.location.href = url;
79+
}
80+
</script>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* 카테고리별 색상 상수 정의 (GitHub 스타일)
3+
*/
4+
export const categoryColorMap: Record<string, string> = {
5+
JavaScript: "#f1e05a",
6+
TypeScript: "#3178c6",
7+
React: "#61dafb",
8+
VueJs: "#4fc08d",
9+
Web: "#e34c26",
10+
Backend: "#0078d4",
11+
default: "#586069",
12+
};
13+
14+
/**
15+
* 카테고리에 해당하는 색상을 가져옵니다
16+
* @param category - 카테고리명
17+
* @returns 해당 카테고리의 색상 또는 기본 색상
18+
*/
19+
export function getCategoryColor(category: string): string {
20+
return categoryColorMap[category] || categoryColorMap.default;
21+
}

.vitepress/theme/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// https://vitepress.dev/guide/custom-theme
2+
import { h } from "vue";
3+
import type { Theme } from "vitepress";
4+
import DefaultTheme from "vitepress/theme";
5+
import BlogList from "./components/BlogList.vue";
6+
import "./style.css";
7+
8+
export default {
9+
extends: DefaultTheme,
10+
Layout: () => {
11+
return h(DefaultTheme.Layout, null, {
12+
// https://vitepress.dev/guide/extending-default-theme#layout-slots
13+
});
14+
},
15+
enhanceApp({ app, router, siteData }) {
16+
app.component("BlogList", BlogList);
17+
},
18+
} satisfies Theme;

0 commit comments

Comments
 (0)