댓글 시스템 복구

This commit is contained in:
2026-04-07 12:44:24 +09:00
parent 2c0b5268fa
commit 6bac13006a
16 changed files with 1403 additions and 15 deletions

View File

@@ -2,8 +2,9 @@
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from './stores/auth'
import { editorNewPath, favoritesPath, followingFeedPath, homePath, loginPath, mePath, templatesPath } from './lib/paths'
import { commentsPath, editorNewPath, favoritesPath, followingFeedPath, homePath, loginPath, mePath, templatesPath } from './lib/paths'
import { displayInitialFrom } from './lib/display'
import { api } from './lib/api'
import { toApiUrl } from './lib/runtime'
import { useToast } from './composables/useToast'
import iconDockToLeft from './assets/icons/dock_to_left.svg'
@@ -40,6 +41,7 @@ const viewportWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 14
const backendState = ref('online')
const backendMessage = ref('')
const isFullscreenActive = ref(false)
const unreadCommentCount = ref(0)
provide('rightRailOpen', rightRailOpen)
provide('localRightRailTarget', '#local-right-rail-root')
@@ -76,6 +78,7 @@ const leftNavItems = computed(() => {
{ key: 'me', label: '나의 티어표', path: '/me', iconSrc: iconLists, requiresAuth: true },
{ key: 'favorites', label: '즐겨찾기', path: '/favorites', iconSrc: iconFavorite, requiresAuth: true },
{ key: 'followingFeed', label: '팔로우 피드', path: '/following', iconSrc: iconKidStar, requiresAuth: true },
{ key: 'comments', label: '댓글 관리', path: commentsPath(), iconSrc: iconMenuBook, requiresAuth: true, showDot: unreadCommentCount.value > 0 },
{ key: 'profile', label: '설정', path: '/profile', iconSrc: iconSettings, requiresAuth: true },
]
return items.filter((item) => !item.requiresAuth || (authReady.value && auth.user))
@@ -261,6 +264,16 @@ const routeMeta = computed(() => {
action: () => router.push(favoritesPath()),
}
}
if (route.name === 'comments') {
return {
title: '댓글 관리',
subtitle: '댓글과 답글 확인',
contextTitle: '알림',
contextText: '내 티어표에 달린 댓글과 내 댓글에 달린 답글을 확인하고 바로 해당 위치로 이동할 수 있어요.',
actionLabel: '나의 티어표 보기',
action: () => router.push(mePath()),
}
}
if (route.name === 'userProfile') {
return {
title: '작성자 프로필',
@@ -313,6 +326,23 @@ function handleBackendStatus(event) {
backendMessage.value = typeof event?.detail?.message === 'string' ? event.detail.message : ''
}
function handleCommentInboxUpdated(event) {
unreadCommentCount.value = Math.max(0, Number(event?.detail?.unreadCount || 0))
}
async function refreshUnreadCommentCount() {
if (!authReady.value || !auth.user) {
unreadCommentCount.value = 0
return
}
try {
const data = await api.getCommentInboxUnreadCount()
unreadCommentCount.value = Math.max(0, Number(data.unreadCount || 0))
} catch (error) {
unreadCommentCount.value = 0
}
}
function syncFullscreenState() {
if (typeof document === 'undefined') return
isFullscreenActive.value = !!(document.fullscreenElement || document.webkitFullscreenElement)
@@ -344,6 +374,7 @@ onMounted(async () => {
syncViewportWidth()
syncFullscreenState()
window.addEventListener('tier-maker:backend-status', handleBackendStatus)
window.addEventListener('tier-maker:comment-inbox-updated', handleCommentInboxUpdated)
window.addEventListener('resize', syncViewportWidth)
window.addEventListener('keydown', handleGlobalKeydown)
document.addEventListener('fullscreenchange', syncFullscreenState)
@@ -360,6 +391,7 @@ onMounted(async () => {
rightRailOpen.value = true
}
searchQuery.value = typeof route.query.q === 'string' ? route.query.q : ''
await refreshUnreadCommentCount()
})
function handleGlobalKeydown(event) {
@@ -423,6 +455,7 @@ async function toggleFullscreen() {
onBeforeUnmount(() => {
if (typeof window !== 'undefined') {
window.removeEventListener('tier-maker:backend-status', handleBackendStatus)
window.removeEventListener('tier-maker:comment-inbox-updated', handleCommentInboxUpdated)
window.removeEventListener('resize', syncViewportWidth)
window.removeEventListener('keydown', handleGlobalKeydown)
document.removeEventListener('fullscreenchange', syncFullscreenState)
@@ -441,6 +474,14 @@ watch(
mobileLeftNavOpen.value = false
rightRailOpen.value = false
}
refreshUnreadCommentCount()
}
)
watch(
() => auth.user?.id,
() => {
refreshUnreadCommentCount()
}
)
@@ -656,6 +697,7 @@ function reloadApp() {
<span class="leftNav__glyph">
<SvgIcon v-if="item.iconSrc" :src="item.iconSrc" :size="24" />
<svg v-else viewBox="0 0 24 24" aria-hidden="true"><path :d="item.icon" /></svg>
<span v-if="item.showDot" class="leftNav__dot" aria-hidden="true"></span>
</span>
<span class="leftNav__label">{{ item.label }}</span>
</RouterLink>
@@ -1260,12 +1302,24 @@ function reloadApp() {
/* width: 28px; */
/* height: 28px; */
/* border-radius: 10px; */
position: relative;
display: grid;
place-items: center;
/* background: rgba(255, 255, 255, 0.06); */
flex: 0 0 auto;
}
.leftNav__dot {
position: absolute;
top: -2px;
right: -3px;
width: 9px;
height: 9px;
border-radius: 999px;
background: #ff4d67;
box-shadow: 0 0 0 2px var(--theme-surface);
}
.appShell--leftCollapsed .leftRail__top {
justify-content: center;
}