댓글 더보기와 컨트롤 정리

This commit is contained in:
2026-04-07 14:09:45 +09:00
parent d9aa6a6922
commit 63dc8f871c
6 changed files with 191 additions and 21 deletions

View File

@@ -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(() => {
<div v-else class="commentsThread">
<article
v-for="comment in comments"
v-for="comment in visibleComments"
:key="comment.id"
class="commentItem"
:class="{ 'commentItem--highlighted': isHighlighted(comment.id) }"
@@ -296,7 +332,7 @@ onBeforeUnmount(() => {
<div v-if="comment.replies?.length" class="replyList">
<article
v-for="reply in comment.replies"
v-for="reply in visibleRepliesOf(comment)"
:key="reply.id"
class="commentItem commentItem--reply"
:class="{ 'commentItem--highlighted': isHighlighted(reply.id) }"
@@ -325,8 +361,14 @@ onBeforeUnmount(() => {
</div>
<div class="commentItem__body">{{ reply.content }}</div>
</article>
<button v-if="hasMoreReplies(comment)" class="commentMoreButton" type="button" @click="showMoreReplies(comment.id)">
답글 {{ remainingReplyCount(comment) }} 보기
</button>
</div>
</article>
<button v-if="hasMoreRootComments" class="commentMoreButton commentMoreButton--root" type="button" @click="showMoreRootComments">
댓글 {{ remainingRootCount }} 보기
</button>
</div>
</section>
</template>
@@ -450,18 +492,34 @@ onBeforeUnmount(() => {
.commentsThread {
display: grid;
gap: 12px;
gap: 10px;
}
.commentMoreButton {
justify-self: flex-start;
margin-top: 10px;
padding: 0;
border: 0;
background: transparent;
color: var(--theme-text-muted);
font-size: 13px;
font-weight: 800;
cursor: pointer;
}
.commentMoreButton--root {
margin-top: 4px;
}
.commentItem {
position: relative;
padding: 14px 0 0;
padding: 12px 0 0;
}
.commentItem--reply {
margin-top: 10px;
margin-left: 22px;
padding-top: 10px;
margin-top: 8px;
margin-left: 20px;
padding-top: 8px;
}
.commentItem--reply::before {
@@ -483,7 +541,7 @@ onBeforeUnmount(() => {
display: flex;
justify-content: space-between;
gap: 12px;
margin-bottom: 8px;
margin-bottom: 6px;
}
.commentItem__author {
@@ -545,8 +603,8 @@ onBeforeUnmount(() => {
}
.commentItem__body {
padding: 14px 15px;
border-radius: 18px;
padding: 12px 14px;
border-radius: 16px;
border: 1px solid var(--theme-border);
background: var(--theme-surface);
white-space: pre-wrap;
@@ -555,8 +613,8 @@ onBeforeUnmount(() => {
}
.replyComposer {
margin-top: 14px;
padding: 14px;
margin-top: 12px;
padding: 12px;
border-radius: 18px;
background: var(--theme-surface-soft);
}