diff --git a/docs/history.md b/docs/history.md index 8a9fbf5..300079a 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-07 v1.1.8 +- 댓글은 처음부터 전부 렌더링하지 않고 일부만 보여준 뒤 `더 보기`로 확장하는 방향을 채택했다. 이 프로젝트는 본문이 긴 티어표 프리뷰와 함께 댓글을 보여주므로, 기본 노출 개수를 제한하는 편이 가독성과 레일 안정성에 모두 유리하다. +- 댓글 관리 화면 컨트롤은 별도 체크박스 문법을 만들지 않고, 설정/에디터에서 이미 쓰는 토글 스위치와 저장 CTA 톤을 재사용하는 것이 일관성에 맞다고 판단했다. + ## 2026-04-07 v1.1.7 - 카드 내부 그리드에서 썸네일 비율을 맞출 때는 `aspect-ratio`만 두지 않고, 부모 그리드의 `stretch` 영향을 함께 차단해야 한다고 정리했다. 댓글 관리 카드 썸네일은 16:9 규칙을 CSS 정렬까지 포함해 고정한다. diff --git a/docs/spec.md b/docs/spec.md index f1c450d..40194a0 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -51,6 +51,7 @@ - 저장된 티어표에는 댓글 스레드가 붙을 수 있다. 작성자 본인 편집 화면에서는 `작업 팁` 아래, 작성자가 아닌 사용자의 보기 전용 화면에서는 `preview` 보드 아래에서 같은 댓글 카드를 사용한다. - 댓글 알림 메뉴는 좌측 사이드 `댓글 관리`로 노출하며, 읽지 않은 댓글이 하나라도 있으면 빨간 dot을 표시한다. - 댓글 정렬은 루트 댓글 최신순, 각 루트 내부의 답글은 오래된순을 기본 규칙으로 유지한다. +- 댓글 표시 밀도 제어를 위해 기본 노출 개수는 루트 댓글 10개, 각 루트의 답글 3개로 제한하고 `더 보기` 버튼으로 추가 노출한다. - 우측 패널 - 현재 화면 문맥에 맞는 설명, 빠른 액션, 계정 상태 같은 보조 정보를 배치한다. - 에디터/관리자 세부 옵션은 후속 단계에서 이 패널로 점진 이관한다. diff --git a/docs/todo.md b/docs/todo.md index 872ec13..2d40c2b 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,6 +1,8 @@ # 할 일 및 이슈 ## 단기 확인 +- `v1.1.8` 이후 댓글 더 보기 규칙(루트 10개, 답글 3개)과 남은 개수 표기가 실제 데이터에서 자연스럽게 동작하는지 확인한다. +- 댓글 관리 화면의 `안 읽은 댓글만 보기` 토글과 `모두 읽음 처리` 버튼이 설정/에디터의 공통 컨트롤 톤과 이질감이 없는지 확인한다. - `v1.1.7` 이후 댓글 관리 카드 썸네일이 실제로 모든 카드에서 16:9로 유지되는지 데스크톱/모바일에서 다시 확인한다. - `v1.1.6` 이후 루트 댓글이 최신순으로, 답글은 오래된순으로 정확히 보이는지 실제 댓글 데이터를 여러 개 넣어 확인한다. - 뷰어 모드에서 댓글이 길어져도 우측 `공유 티어표 보기` 카드가 스폰서 카드 바로 아래에서 유지되고, 더 이상 하단으로 밀려 보이지 않는지 확인한다. diff --git a/docs/update.md b/docs/update.md index 37502b5..ebbe29e 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,11 @@ # 업데이트 로그 +## 2026-04-07 v1.1.8 +- 댓글 관리 화면의 상단 컨트롤을 정리했다. `안 읽은 댓글만 보기`는 체크박스 대신 프로젝트 공통 토글 스위치 문법으로 바꾸고, `모두 읽음 처리` 버튼도 저장 CTA 계열과 같은 톤으로 맞췄다. +- 댓글 영역에는 `더 보기` 흐름을 추가했다. 루트 댓글은 처음 10개, 답글은 처음 3개만 보여주고, 남은 개수를 표시하는 `댓글 n개 더 보기`, `답글 n개 더 보기` 버튼으로 단계적으로 펼친다. +- 댓글 카드 세로 밀도도 함께 낮췄다. 댓글/답글 카드 간격과 본문 패딩, 답글 입력 카드 여백을 줄여 긴 스레드에서도 화면을 덜 차지하게 정리했다. +- 확인: `npm run build` + ## 2026-04-07 v1.1.7 - 댓글 관리 카드 썸네일이 CSS grid의 세로 stretch 영향으로 16:9로 보이지 않을 수 있어, 썸네일 셀을 `align-self: start`로 고정하고 카드 본문 정렬도 `align-items: start`로 바꿨다. 이제 댓글 관리 썸네일은 항상 16:9 비율로 표시된다. - 확인: `npm run build` diff --git a/frontend/src/components/TierListCommentsCard.vue b/frontend/src/components/TierListCommentsCard.vue index 983a503..fae5cfc 100644 --- a/frontend/src/components/TierListCommentsCard.vue +++ b/frontend/src/components/TierListCommentsCard.vue @@ -39,11 +39,15 @@ const openedReplyComposerId = ref('') const submittingTargetId = ref('') const deletingCommentId = ref('') const replyInputRefs = ref({}) +const visibleRootCount = ref(10) +const visibleReplyCounts = ref({}) let activeCommentRetryTimer = 0 const totalCommentCount = computed(() => comments.value.reduce((count, comment) => count + 1 + (comment.replies?.length || 0), 0) ) +const visibleComments = computed(() => comments.value.slice(0, visibleRootCount.value)) +const hasMoreRootComments = computed(() => comments.value.length > visibleRootCount.value) const loginTarget = computed(() => loginPath(route.fullPath)) const highlightedCommentId = computed(() => typeof route.query.commentId === 'string' ? route.query.commentId.trim() : '' @@ -110,6 +114,8 @@ async function loadComments() { try { const data = await api.listTierListComments(props.tierListId) comments.value = Array.isArray(data.comments) ? data.comments : [] + visibleRootCount.value = 10 + visibleReplyCounts.value = {} scrollToHighlightedComment() } catch (loadError) { error.value = '댓글을 불러오지 못했어요.' @@ -118,6 +124,36 @@ async function loadComments() { } } +function visibleRepliesOf(comment) { + const replies = Array.isArray(comment?.replies) ? comment.replies : [] + const limit = visibleReplyCounts.value[comment?.id] || 3 + return replies.slice(0, limit) +} + +function hasMoreReplies(comment) { + const replies = Array.isArray(comment?.replies) ? comment.replies : [] + return replies.length > (visibleReplyCounts.value[comment?.id] || 3) +} + +function remainingReplyCount(comment) { + const replies = Array.isArray(comment?.replies) ? comment.replies : [] + return Math.max(0, replies.length - (visibleReplyCounts.value[comment?.id] || 3)) +} + +const remainingRootCount = computed(() => Math.max(0, comments.value.length - visibleRootCount.value)) + +function showMoreRootComments() { + visibleRootCount.value += 10 +} + +function showMoreReplies(commentId) { + if (!commentId) return + visibleReplyCounts.value = { + ...visibleReplyCounts.value, + [commentId]: (visibleReplyCounts.value[commentId] || 3) + 3, + } +} + async function submitComment(parentCommentId = '') { if (!props.canWrite || !props.tierListId) return const isReply = !!parentCommentId @@ -247,7 +283,7 @@ onBeforeUnmount(() => {