관리자 통계 제외와 콘텐츠 링크 정리
This commit is contained in:
@@ -159,6 +159,13 @@ const getPageStatusClass = (page) => {
|
||||
return 'text-[#99A3AD]'
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지 공개 여부 확인
|
||||
* @param {Object} page - 페이지
|
||||
* @returns {boolean} 공개 여부
|
||||
*/
|
||||
const isPublicPage = (page) => page.status === 'published'
|
||||
|
||||
/**
|
||||
* 날짜 표시 형식 변환
|
||||
* @param {string | null} value - ISO 날짜 문자열
|
||||
@@ -302,14 +309,29 @@ const deletePage = async (page) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="admin-pages__table-body divide-y divide-line bg-white">
|
||||
<tr v-for="page in pages" :key="page.id" class="admin-pages__row">
|
||||
<tr v-for="page in pages" :key="page.id" class="admin-pages__row group">
|
||||
<td class="admin-pages__cell px-4 py-4">
|
||||
<NuxtLink class="admin-pages__title-link font-semibold hover:opacity-70" :to="`/admin/pages/${page.id}`">
|
||||
{{ page.title }}
|
||||
</NuxtLink>
|
||||
<p class="admin-pages__slug mt-1 text-xs text-muted">
|
||||
/pages/{{ page.slug }}
|
||||
</p>
|
||||
<div class="admin-pages__slug-row mt-1 flex items-center gap-1 text-xs text-muted">
|
||||
<span class="admin-pages__slug truncate">/pages/{{ page.slug }}</span>
|
||||
<NuxtLink
|
||||
v-if="isPublicPage(page)"
|
||||
class="admin-pages__preview-link inline-flex size-5 items-center justify-center rounded text-[#8e9cac] opacity-0 transition-colors hover:text-[#394047] focus:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#8e9cac] group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
:to="`/pages/${page.slug}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="공개 페이지 보기"
|
||||
title="공개 페이지 보기"
|
||||
@click.stop
|
||||
>
|
||||
<svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M17 7 7 17" />
|
||||
<path d="M8 7h9v9" />
|
||||
</svg>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</td>
|
||||
<td class="admin-pages__cell px-4 py-4">
|
||||
<span class="admin-pages__status-text text-xs" :class="getPageStatusClass(page)">
|
||||
|
||||
@@ -224,7 +224,7 @@ const getPostStatusLabel = (post) => {
|
||||
}
|
||||
|
||||
if (statusKey === 'published') {
|
||||
return '발행'
|
||||
return '공개'
|
||||
}
|
||||
|
||||
if (statusKey === 'members') {
|
||||
@@ -728,7 +728,7 @@ watch(openPostMenuId, async (postId) => {
|
||||
<span class="admin-posts__filter-select-wrap relative block">
|
||||
<select v-model="statusFilter" class="admin-posts__filter-select h-10 appearance-none rounded border border-line bg-white pl-3 pr-9 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<option value="all">전체 상태</option>
|
||||
<option value="published">발행</option>
|
||||
<option value="published">공개</option>
|
||||
<option value="draft">초안</option>
|
||||
<option value="scheduled">예약</option>
|
||||
<option value="members">멤버십</option>
|
||||
@@ -802,7 +802,7 @@ watch(openPostMenuId, async (postId) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="admin-posts__table-body divide-y divide-line bg-white">
|
||||
<tr v-for="post in filteredPosts" :key="post.id" class="admin-posts__row">
|
||||
<tr v-for="post in filteredPosts" :key="post.id" class="admin-posts__row group">
|
||||
<td class="admin-posts__cell admin-posts__cell-featured w-10 px-2 py-4 text-center">
|
||||
<span
|
||||
v-if="post.isFeatured"
|
||||
@@ -835,9 +835,24 @@ watch(openPostMenuId, async (postId) => {
|
||||
댓글 {{ post.commentCount || 0 }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="admin-posts__slug mt-1 text-xs text-muted">
|
||||
/post/{{ post.slug }}
|
||||
</p>
|
||||
<div class="admin-posts__slug-row mt-1 flex items-center gap-1 text-xs text-muted">
|
||||
<span class="admin-posts__slug truncate">/post/{{ post.slug }}</span>
|
||||
<NuxtLink
|
||||
v-if="isPublicPost(post)"
|
||||
class="admin-posts__preview-link inline-flex size-5 items-center justify-center rounded text-[#8e9cac] opacity-0 transition-colors hover:text-[#394047] focus:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#8e9cac] group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
:to="`/post/${post.slug}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="공개 글 보기"
|
||||
title="공개 글 보기"
|
||||
@click.stop
|
||||
>
|
||||
<svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M17 7 7 17" />
|
||||
<path d="M8 7h9v9" />
|
||||
</svg>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</td>
|
||||
<td class="admin-posts__cell px-4 py-4">
|
||||
<span
|
||||
|
||||
@@ -156,6 +156,7 @@ const mapLatestPost = (post) => {
|
||||
featuredImage: post.featuredImage,
|
||||
featuredImageThumbnail: post.featuredImageThumbnail || '',
|
||||
tagName: tagMeta.name,
|
||||
tagSlug: primaryTagSlug || '',
|
||||
tagColor: tagMeta.color,
|
||||
publishedAt: formatPostDate(post.publishedAt),
|
||||
publishedAtIso: post.publishedAt || '',
|
||||
@@ -539,7 +540,16 @@ watch([latestPosts, hasHomeInfeedAd], () => {
|
||||
<time v-if="post.publishedAt" :datetime="post.publishedAtIso">{{ post.publishedAt }}</time>
|
||||
<template v-if="post.tagName">
|
||||
<span v-if="post.publishedAt" class="text-[var(--site-line)]">/</span>
|
||||
<NuxtLink
|
||||
v-if="post.tagSlug"
|
||||
class="rounded-md px-1.5 py-px font-medium text-[var(--site-text)]"
|
||||
:style="{ backgroundColor: `${post.tagColor}1a` }"
|
||||
:to="`/tag/${post.tagSlug}`"
|
||||
>
|
||||
{{ post.tagName }}
|
||||
</NuxtLink>
|
||||
<span
|
||||
v-else
|
||||
class="rounded-md px-1.5 py-px font-medium text-[var(--site-text)]"
|
||||
:style="{ backgroundColor: `${post.tagColor}1a` }"
|
||||
>
|
||||
@@ -556,11 +566,11 @@ watch([latestPosts, hasHomeInfeedAd], () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<NuxtLink
|
||||
class="absolute top-0 right-0 flex cursor-pointer items-center gap-1 hover:opacity-75"
|
||||
:class="isPostFeedCards ? '' : 'md:top-auto md:right-0 md:bottom-0 md:invisible md:opacity-0 md:transition-[opacity,visibility] md:duration-200 md:group-hover:visible md:group-hover:opacity-100'"
|
||||
type="button"
|
||||
aria-label="Share this post"
|
||||
:to="post.to"
|
||||
aria-label="게시물 보기"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -575,7 +585,7 @@ watch([latestPosts, hasHomeInfeedAd], () => {
|
||||
<path d="M17 7 7 17" />
|
||||
<path d="M8 7h9v9" />
|
||||
</svg>
|
||||
</button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
@@ -8,30 +8,43 @@ const { data: tags } = await useFetch('/api/tags?tagType=all', {
|
||||
})
|
||||
|
||||
/**
|
||||
* 태그 슬러그로 공개 태그 이름을 찾는다.
|
||||
* 태그 슬러그로 공개 태그 정보를 찾는다.
|
||||
* @param {string | undefined} slug - 태그 슬러그
|
||||
* @returns {string} 태그 이름
|
||||
* @returns {{ name: string, slug: string }} 태그 정보
|
||||
*/
|
||||
const getTagName = (slug) => {
|
||||
const getTagMeta = (slug) => {
|
||||
if (!slug) {
|
||||
return ''
|
||||
return {
|
||||
name: '',
|
||||
slug: ''
|
||||
}
|
||||
}
|
||||
|
||||
return tags.value.find((tag) => tag.slug === slug)?.name || String(slug).toUpperCase()
|
||||
const matchedTag = tags.value.find((tag) => tag.slug === slug)
|
||||
|
||||
return {
|
||||
name: matchedTag?.name || String(slug).toUpperCase(),
|
||||
slug
|
||||
}
|
||||
}
|
||||
|
||||
const postCards = computed(() => posts.value.map((post) => ({
|
||||
title: post.title,
|
||||
excerpt: createPostSummary(post.excerpt, post.content, {
|
||||
maxLength: 320,
|
||||
appendEllipsis: false
|
||||
}),
|
||||
featuredImage: post.featuredImage,
|
||||
featuredImageThumbnail: post.featuredImageThumbnail || '',
|
||||
tag: getTagName(post.tags?.[0]),
|
||||
publishedAt: formatPostDate(post.publishedAt),
|
||||
to: `/post/${post.slug}`
|
||||
})))
|
||||
const postCards = computed(() => posts.value.map((post) => {
|
||||
const primaryTag = getTagMeta(post.tags?.[0])
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
excerpt: createPostSummary(post.excerpt, post.content, {
|
||||
maxLength: 320,
|
||||
appendEllipsis: false
|
||||
}),
|
||||
featuredImage: post.featuredImage,
|
||||
featuredImageThumbnail: post.featuredImageThumbnail || '',
|
||||
tag: primaryTag.name,
|
||||
tagSlug: primaryTag.slug,
|
||||
publishedAt: formatPostDate(post.publishedAt),
|
||||
to: `/post/${post.slug}`
|
||||
}
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -35,6 +35,7 @@ const tagPosts = computed(() => posts.value
|
||||
featuredImage: post.featuredImage,
|
||||
featuredImageThumbnail: post.featuredImageThumbnail || '',
|
||||
tag: primaryTag?.name || primaryTagSlug.toUpperCase(),
|
||||
tagSlug: primaryTagSlug,
|
||||
tagColor: primaryTag?.color || '#4d4d4d',
|
||||
isFeatured: Boolean(post.isFeatured),
|
||||
commentCount: Number(post.commentCount || 0),
|
||||
@@ -105,12 +106,13 @@ const tagPosts = computed(() => posts.value
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs site-muted sm:gap-1.5">
|
||||
<time v-if="post.publishedAt" :datetime="post.publishedAtIso">{{ post.publishedAt }}</time>
|
||||
<span class="text-[var(--site-line)]">/</span>
|
||||
<span
|
||||
<NuxtLink
|
||||
class="rounded-md px-1.5 py-px font-medium text-[var(--site-text)]"
|
||||
:style="{ backgroundColor: `${post.tagColor}1a` }"
|
||||
:to="`/tag/${post.tagSlug}`"
|
||||
>
|
||||
{{ post.tag }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
<span class="text-[var(--site-line)]">/</span>
|
||||
<span class="flex items-center gap-0.75">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="-mt-px">
|
||||
@@ -120,10 +122,10 @@ const tagPosts = computed(() => posts.value
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
<NuxtLink
|
||||
class="absolute top-0 right-0 flex cursor-pointer items-center gap-1 hover:opacity-75 md:top-auto md:right-0 md:bottom-0 md:invisible md:opacity-0 md:transition-[opacity,visibility] md:duration-200 md:group-hover:visible md:group-hover:opacity-100"
|
||||
type="button"
|
||||
aria-label="Share this post"
|
||||
:to="post.to"
|
||||
aria-label="게시물 보기"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -138,7 +140,7 @@ const tagPosts = computed(() => posts.value
|
||||
<path d="M17 7 7 17" />
|
||||
<path d="M8 7h9v9" />
|
||||
</svg>
|
||||
</button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
Reference in New Issue
Block a user