공개 상세 경로와 새 글 에디터 보정
This commit is contained in:
@@ -81,7 +81,7 @@ const deletePost = async () => {
|
||||
<NuxtLink
|
||||
v-if="post.status === 'published'"
|
||||
class="admin-post-edit__view rounded border border-line bg-white px-4 py-2 text-sm font-semibold"
|
||||
:to="`/posts/${post.slug}`"
|
||||
:to="`/post/${post.slug}`"
|
||||
target="_blank"
|
||||
>
|
||||
보기
|
||||
|
||||
@@ -92,7 +92,7 @@ const deletePost = async (post) => {
|
||||
{{ post.title }}
|
||||
</NuxtLink>
|
||||
<p class="admin-posts__slug mt-1 text-xs text-muted">
|
||||
/posts/{{ post.slug }}
|
||||
/post/{{ post.slug }}
|
||||
</p>
|
||||
</td>
|
||||
<td class="admin-posts__cell px-4 py-4">
|
||||
|
||||
@@ -31,7 +31,7 @@ const mapPostCard = (post) => ({
|
||||
excerpt: post.excerpt,
|
||||
tag: post.tags?.[0]?.toUpperCase() || 'POST',
|
||||
publishedAt: formatPostDate(post.publishedAt),
|
||||
to: `/posts/${post.slug}`
|
||||
to: `/post/${post.slug}`
|
||||
})
|
||||
|
||||
const postCards = computed(() => posts.value.map(mapPostCard))
|
||||
|
||||
34
pages/post/[slug].vue
Normal file
34
pages/post/[slug].vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
layout: 'post'
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const slug = computed(() => String(route.params.slug || ''))
|
||||
|
||||
const { data: post } = await useFetch(() => `/api/posts/${slug.value}`)
|
||||
|
||||
if (!post.value) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: '게시물을 찾을 수 없습니다.'
|
||||
})
|
||||
}
|
||||
|
||||
const postTag = computed(() => post.value.tags?.[0]?.toUpperCase() || 'POST')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentRenderer>
|
||||
<ProseHeaderCard>
|
||||
<p class="post-detail__eyebrow text-sm uppercase text-white/70">
|
||||
{{ postTag }}
|
||||
</p>
|
||||
<h1 class="post-detail__title mt-3 text-4xl font-semibold leading-tight">
|
||||
{{ post.title }}
|
||||
</h1>
|
||||
</ProseHeaderCard>
|
||||
|
||||
<ContentMarkdownRenderer class="post-detail__content" :content="post.content" />
|
||||
</ContentRenderer>
|
||||
</template>
|
||||
@@ -1,34 +1,12 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
layout: 'post'
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const slug = computed(() => String(route.params.slug || ''))
|
||||
const slug = String(route.params.slug || '')
|
||||
|
||||
const { data: post } = await useFetch(() => `/api/posts/${slug.value}`)
|
||||
|
||||
if (!post.value) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: '게시물을 찾을 수 없습니다.'
|
||||
})
|
||||
}
|
||||
|
||||
const postTag = computed(() => post.value.tags?.[0]?.toUpperCase() || 'POST')
|
||||
await navigateTo(`/post/${slug}`, {
|
||||
redirectCode: 301
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentRenderer>
|
||||
<ProseHeaderCard>
|
||||
<p class="post-detail__eyebrow text-sm uppercase text-white/70">
|
||||
{{ postTag }}
|
||||
</p>
|
||||
<h1 class="post-detail__title mt-3 text-4xl font-semibold leading-tight">
|
||||
{{ post.title }}
|
||||
</h1>
|
||||
</ProseHeaderCard>
|
||||
|
||||
<ContentMarkdownRenderer class="post-detail__content" :content="post.content" />
|
||||
</ContentRenderer>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
48
pages/posts/index.vue
Normal file
48
pages/posts/index.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
const { data: posts } = await useFetch('/api/posts', {
|
||||
default: () => []
|
||||
})
|
||||
|
||||
/**
|
||||
* 날짜 표시 형식 변환
|
||||
* @param {string | null} value - ISO 날짜 문자열
|
||||
* @returns {string} 화면 표시 날짜
|
||||
*/
|
||||
const formatPostDate = (value) => {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const date = new Date(value)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
|
||||
return `${year}.${month}.${day}`
|
||||
}
|
||||
|
||||
const postCards = computed(() => posts.value.map((post) => ({
|
||||
title: post.title,
|
||||
excerpt: post.excerpt,
|
||||
tag: post.tags?.[0]?.toUpperCase() || 'POST',
|
||||
publishedAt: formatPostDate(post.publishedAt),
|
||||
to: `/post/${post.slug}`
|
||||
})))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainColumn>
|
||||
<section class="posts-index site-section">
|
||||
<div class="posts-index__header site-section-header">
|
||||
<p class="posts-index__eyebrow text-xs font-semibold uppercase text-muted">
|
||||
Posts
|
||||
</p>
|
||||
<h1 class="posts-index__title mt-3 text-4xl font-semibold leading-tight">
|
||||
게시물
|
||||
</h1>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<PostCard v-for="post in postCards" :key="post.to" :post="post" />
|
||||
</MainColumn>
|
||||
</template>
|
||||
57
pages/tag/[slug].vue
Normal file
57
pages/tag/[slug].vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
const slug = computed(() => String(route.params.slug || ''))
|
||||
|
||||
const { data: tags } = await useFetch('/api/tags', {
|
||||
default: () => []
|
||||
})
|
||||
|
||||
const { data: posts } = await useFetch('/api/posts', {
|
||||
default: () => []
|
||||
})
|
||||
|
||||
/**
|
||||
* 날짜 표시 형식 변환
|
||||
* @param {string | null} value - ISO 날짜 문자열
|
||||
* @returns {string} 화면 표시 날짜
|
||||
*/
|
||||
const formatPostDate = (value) => {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const date = new Date(value)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
|
||||
return `${year}.${month}.${day}`
|
||||
}
|
||||
|
||||
const tag = computed(() => tags.value.find((item) => item.slug === slug.value))
|
||||
|
||||
const tagPosts = computed(() => posts.value
|
||||
.filter((post) => post.tags.includes(slug.value))
|
||||
.map((post) => ({
|
||||
title: post.title,
|
||||
excerpt: post.excerpt,
|
||||
tag: tag.value?.name || slug.value.toUpperCase(),
|
||||
publishedAt: formatPostDate(post.publishedAt),
|
||||
to: `/post/${post.slug}`
|
||||
})))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainColumn>
|
||||
<TagHeader
|
||||
:title="tag?.name || slug.toUpperCase()"
|
||||
:description="tag?.description || ''"
|
||||
/>
|
||||
<PostCard v-for="post in tagPosts" :key="post.to" :post="post" />
|
||||
<section v-if="tagPosts.length === 0" class="tag-posts site-section">
|
||||
<div class="tag-posts__empty site-section-body text-sm text-muted">
|
||||
이 태그에 연결된 글이 없습니다.
|
||||
</div>
|
||||
</section>
|
||||
</MainColumn>
|
||||
</template>
|
||||
@@ -1,57 +1,12 @@
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
const slug = computed(() => String(route.params.slug || ''))
|
||||
const slug = String(route.params.slug || '')
|
||||
|
||||
const { data: tags } = await useFetch('/api/tags', {
|
||||
default: () => []
|
||||
await navigateTo(`/tag/${slug}`, {
|
||||
redirectCode: 301
|
||||
})
|
||||
|
||||
const { data: posts } = await useFetch('/api/posts', {
|
||||
default: () => []
|
||||
})
|
||||
|
||||
/**
|
||||
* 날짜 표시 형식 변환
|
||||
* @param {string | null} value - ISO 날짜 문자열
|
||||
* @returns {string} 화면 표시 날짜
|
||||
*/
|
||||
const formatPostDate = (value) => {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const date = new Date(value)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
|
||||
return `${year}.${month}.${day}`
|
||||
}
|
||||
|
||||
const tag = computed(() => tags.value.find((item) => item.slug === slug.value))
|
||||
|
||||
const tagPosts = computed(() => posts.value
|
||||
.filter((post) => post.tags.includes(slug.value))
|
||||
.map((post) => ({
|
||||
title: post.title,
|
||||
excerpt: post.excerpt,
|
||||
tag: tag.value?.name || slug.value.toUpperCase(),
|
||||
publishedAt: formatPostDate(post.publishedAt),
|
||||
to: `/posts/${post.slug}`
|
||||
})))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainColumn>
|
||||
<TagHeader
|
||||
:title="tag?.name || slug.toUpperCase()"
|
||||
:description="tag?.description || ''"
|
||||
/>
|
||||
<PostCard v-for="post in tagPosts" :key="post.to" :post="post" />
|
||||
<section v-if="tagPosts.length === 0" class="tag-posts site-section">
|
||||
<div class="tag-posts__empty site-section-body text-sm text-muted">
|
||||
이 태그에 연결된 글이 없습니다.
|
||||
</div>
|
||||
</section>
|
||||
</MainColumn>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
45
pages/tags/index.vue
Normal file
45
pages/tags/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
const { data: tags } = await useFetch('/api/tags', {
|
||||
default: () => []
|
||||
})
|
||||
|
||||
const { data: posts } = await useFetch('/api/posts', {
|
||||
default: () => []
|
||||
})
|
||||
|
||||
/**
|
||||
* 태그에 연결된 게시물 수 조회
|
||||
* @param {string} slug - 태그 슬러그
|
||||
* @returns {number} 게시물 수
|
||||
*/
|
||||
const getPostCount = (slug) => posts.value.filter((post) => post.tags.includes(slug)).length
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainColumn>
|
||||
<section class="tags-index site-section">
|
||||
<div class="tags-index__header site-section-header">
|
||||
<p class="tags-index__eyebrow text-xs font-semibold uppercase text-muted">
|
||||
Tags
|
||||
</p>
|
||||
<h1 class="tags-index__title mt-3 text-4xl font-semibold leading-tight">
|
||||
태그
|
||||
</h1>
|
||||
</div>
|
||||
<div class="tags-index__body site-section-body grid gap-3">
|
||||
<NuxtLink
|
||||
v-for="tag in tags"
|
||||
:key="tag.id"
|
||||
class="tags-index__item flex items-center justify-between gap-4 rounded border border-line bg-white px-4 py-3 hover:opacity-80"
|
||||
:to="`/tag/${tag.slug}`"
|
||||
>
|
||||
<span class="tags-index__name flex items-center gap-3 font-semibold">
|
||||
<span class="tags-index__color h-4 w-1 rounded-full" :style="{ backgroundColor: tag.color }" />
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
<span class="tags-index__count text-sm text-muted">{{ getPostCount(tag.slug) }} posts</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</MainColumn>
|
||||
</template>
|
||||
Reference in New Issue
Block a user