Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5575d3028 | |||
| 173f547d8b |
@@ -268,8 +268,10 @@ function mapCommentNotificationRow(row) {
|
||||
topicSlug: row.topic_slug || row.topic_id,
|
||||
topicName: row.topic_name || '',
|
||||
tierListTitle: row.tierlist_title || '',
|
||||
tierListThumbnailSrc: row.tierlist_thumbnail_src || '',
|
||||
commentId: row.comment_id,
|
||||
parentCommentId: row.parent_comment_id || '',
|
||||
parentCommentContent: row.parent_comment_content || '',
|
||||
notificationType: row.notification_type || 'tierlist_comment',
|
||||
isRead: !!row.is_read,
|
||||
readAt: Number(row.read_at || 0),
|
||||
@@ -2843,15 +2845,18 @@ async function listCommentNotifications(userId, { unreadOnly = false } = {}) {
|
||||
n.actor_user_id,
|
||||
c.parent_comment_id,
|
||||
c.content AS comment_content,
|
||||
parent.content AS parent_comment_content,
|
||||
t.topic_id,
|
||||
tp.slug AS topic_slug,
|
||||
tp.name AS topic_name,
|
||||
t.title AS tierlist_title,
|
||||
t.thumbnail_src AS tierlist_thumbnail_src,
|
||||
actor.nickname AS actor_nickname,
|
||||
actor.email AS actor_email,
|
||||
actor.avatar_src AS actor_avatar_src
|
||||
FROM comment_notifications n
|
||||
INNER JOIN tierlist_comments c ON c.id = n.comment_id
|
||||
LEFT JOIN tierlist_comments parent ON parent.id = c.parent_comment_id
|
||||
INNER JOIN tierlists t ON t.id = n.tierlist_id
|
||||
INNER JOIN topics tp ON tp.id = t.topic_id
|
||||
INNER JOIN users actor ON actor.id = n.actor_user_id
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 2026-04-07 v1.1.4
|
||||
- 댓글 관리함은 단순 목록보다 `무슨 티어표에서`, `원래 어떤 댓글이 있었고`, `새로 무엇이 달렸는지`를 한눈에 이해하는 정보 구조가 중요하다고 판단했다. 그래서 썸네일 + 스레드 비교 블록을 기본 카드 문법으로 채택했다.
|
||||
- 댓글 본문과 답글도 단순 들여쓰기보다 카드/말풍선/연결선으로 관계를 보여주는 쪽이 최신 UI 감각에 더 맞는다고 보고, reply depth 1단 구조에 맞춘 시각 문법을 적용했다.
|
||||
|
||||
## 2026-04-07 v1.1.3
|
||||
- 댓글 답글 입력창은 포커스 상태에만 의존하지 않고, 비포커스 상태에서도 시각적 경계를 명확히 주기로 했다. 댓글 UI는 에디터 안의 부가 기능이지만 사용자가 바로 이해할 수 있어야 하므로 카드형 배경과 기본 테두리를 유지한다.
|
||||
|
||||
## 2026-04-07 v1.1.2
|
||||
- 댓글/알림 기능처럼 새 테이블을 뒤늦게 붙이는 경우 `CREATE TABLE IF NOT EXISTS`만으로는 충분하지 않다고 판단했다. 이미 남아 있는 예전 스키마와 충돌할 수 있으므로, 서버 시작 시 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` 형태의 점진 마이그레이션을 함께 넣는 방향으로 유지한다.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
## `/comments`
|
||||
- 화면 파일: `frontend/src/views/CommentInboxView.vue`
|
||||
- 역할: 내 티어표에 달린 댓글과 내 댓글에 달린 답글을 시간순 카드로 확인, 안 읽은 댓글만 보기 필터, 모두 읽음 처리, 카드별 red dot 표시, 카드 클릭 시 해당 티어표의 특정 댓글 위치로 이동
|
||||
- 역할: 내 티어표에 달린 댓글과 내 댓글에 달린 답글을 시간순 카드로 확인, 안 읽은 댓글만 보기 필터, 모두 읽음 처리, 카드별 red dot 표시, 티어표 썸네일과 원댓글/새 댓글 비교 블록 제공, 카드 클릭 시 해당 티어표의 특정 댓글 위치로 이동
|
||||
- 연동 API: `GET /api/comments/inbox`, `GET /api/comments/inbox/unread-count`, `POST /api/comments/inbox/read`
|
||||
|
||||
## `/login`
|
||||
|
||||
@@ -243,6 +243,7 @@
|
||||
- `DELETE /api/users/:userId/follow`
|
||||
- 댓글 알림
|
||||
- `GET /api/comments/inbox`
|
||||
- 알림 카드 렌더링을 위해 티어표 썸네일과 부모 댓글 내용도 함께 반환한다.
|
||||
- `GET /api/comments/inbox/unread-count`
|
||||
- `POST /api/comments/inbox/read`
|
||||
- 관리자
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# 할 일 및 이슈
|
||||
|
||||
## 단기 확인
|
||||
- `v1.1.4` 이후 댓글 관리 카드에서 티어표 썸네일, 원댓글/새 댓글 비교 블록이 데스크톱과 모바일에서 모두 자연스럽게 보이는지 확인한다.
|
||||
- 댓글 스레드 카드 리디자인 후 답글 연결선, 배지, 본문 말풍선 배경이 라이트/다크 모드 모두에서 과하지 않게 보이는지 확인한다.
|
||||
- `v1.1.3` 이후 답글 작성 시 입력창이 열리자마자 포커스를 받고, 포커스 전에도 카드/입력 경계가 분명하게 보이는지 다크/라이트 모드 모두에서 확인한다.
|
||||
- `v1.1.2` 반영 후 실제 운영/로컬 DB에서 서버를 다시 띄워 `comment_notifications.is_read` 컬럼이 자동 보강되는지, `댓글 관리` 메뉴 unread dot과 `/api/comments/inbox/unread-count`가 더 이상 SQL 오류 없이 동작하는지 확인한다.
|
||||
- `v1.1.1` 댓글 복구 이후 다음 흐름을 우선 QA한다: 공개 티어표 프리뷰 하단 댓글 노출, 댓글 작성/답글 작성/본인 댓글 삭제, 댓글 관리 메뉴 red dot, 댓글 관리 화면에서 `안 읽은 댓글만 보기`와 `모두 읽음 처리`, 카드 클릭 후 해당 댓글 위치 스크롤.
|
||||
- 작성자 본인 티어표 편집 화면과 타인 티어표 프리뷰 화면에서 같은 댓글 카드가 모두 자연스럽게 보이는지, 새로고침 후에도 기존 에디터 회귀 없이 댓글 카드만 안정적으로 붙는지 확인한다.
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 2026-04-07 v1.1.4
|
||||
- 댓글 관리 카드 디자인을 확장했다. 각 카드에 해당 티어표 썸네일을 붙이고, `원래 댓글`과 `새 댓글/새 답글`을 한 번에 비교해서 볼 수 있게 스레드 블록 구조로 바꿨다.
|
||||
- 댓글 알림 조회 API는 이제 티어표 썸네일과 부모 댓글 내용을 함께 내려준다. 답글 알림에서는 어떤 댓글에 어떤 답글이 달렸는지 바로 읽을 수 있다.
|
||||
- 일반 댓글 카드(`commentItem`)도 더 카드형이고 세련된 톤으로 정리했다. 본문은 말풍선처럼 분리하고, 답글은 얇은 연결선과 보조 배지로 관계가 자연스럽게 읽히도록 다듬었다.
|
||||
- 확인: `node --check backend/src/db.js`, `npm run build`
|
||||
|
||||
## 2026-04-07 v1.1.3
|
||||
- 댓글 답글 입력 UX를 다듬었다. `답글` 버튼을 누르면 입력창이 열리자마자 자동으로 포커스가 이동하고, 포커스 전에도 구분이 되도록 답글 입력 영역 카드와 textarea 기본 경계선을 보강했다.
|
||||
- 답글 등록 버튼도 기존의 작은 기본형 버튼 대신 프로젝트 전반의 저장 계열 CTA 문법과 같은 `btn--save` 스타일로 맞췄다.
|
||||
- 확인: `npm run build`
|
||||
|
||||
## 2026-04-07 v1.1.2
|
||||
- 댓글 알림 테이블을 기존 DB에서도 안전하게 올릴 수 있도록 스키마 보정 로직을 추가했다. 예전 형태의 `comment_notifications` 또는 `tierlist_comments` 테이블이 이미 있어도 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`로 `is_read`, `read_at`, `notification_type`, `actor_user_id`, `parent_comment_id`, `updated_at`를 보강한다.
|
||||
- 이 수정으로 기존 DB에서 `/api/comments/inbox/unread-count` 호출 시 `Unknown column 'is_read' in 'WHERE'`가 나던 문제를 해결한다.
|
||||
|
||||
@@ -38,6 +38,7 @@ const replyDrafts = ref({})
|
||||
const openedReplyComposerId = ref('')
|
||||
const submittingTargetId = ref('')
|
||||
const deletingCommentId = ref('')
|
||||
const replyInputRefs = ref({})
|
||||
let activeCommentRetryTimer = 0
|
||||
|
||||
const totalCommentCount = computed(() =>
|
||||
@@ -169,8 +170,33 @@ async function deleteComment(commentId) {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleReplyComposer(commentId) {
|
||||
openedReplyComposerId.value = openedReplyComposerId.value === commentId ? '' : commentId
|
||||
function registerReplyInput(commentId, element) {
|
||||
if (!commentId) return
|
||||
if (element) {
|
||||
replyInputRefs.value[commentId] = element
|
||||
return
|
||||
}
|
||||
delete replyInputRefs.value[commentId]
|
||||
}
|
||||
|
||||
async function focusReplyInput(commentId) {
|
||||
if (!commentId) return
|
||||
await nextTick()
|
||||
const target = replyInputRefs.value[commentId]
|
||||
if (target && typeof target.focus === 'function') {
|
||||
target.focus()
|
||||
const value = target.value || ''
|
||||
if (typeof target.setSelectionRange === 'function') {
|
||||
target.setSelectionRange(value.length, value.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleReplyComposer(commentId) {
|
||||
const nextId = openedReplyComposerId.value === commentId ? '' : commentId
|
||||
openedReplyComposerId.value = nextId
|
||||
if (!nextId) return
|
||||
await focusReplyInput(nextId)
|
||||
}
|
||||
|
||||
watch(() => props.tierListId, loadComments, { immediate: true })
|
||||
@@ -232,7 +258,10 @@ onBeforeUnmount(() => {
|
||||
<img v-if="avatarUrlOf(comment)" class="commentItem__avatar" :src="avatarUrlOf(comment)" :alt="displayNameOf(comment)" draggable="false" />
|
||||
<div v-else class="commentItem__avatar commentItem__avatar--fallback">{{ avatarFallbackOf(comment) }}</div>
|
||||
<div class="commentItem__meta">
|
||||
<div class="commentItem__name">{{ displayNameOf(comment) }}</div>
|
||||
<div class="commentItem__metaTop">
|
||||
<div class="commentItem__name">{{ displayNameOf(comment) }}</div>
|
||||
<span class="commentItem__badge">댓글</span>
|
||||
</div>
|
||||
<div class="commentItem__date">{{ formatDate(comment.createdAt) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -254,6 +283,7 @@ onBeforeUnmount(() => {
|
||||
<div v-if="openedReplyComposerId === comment.id && props.canWrite" class="replyComposer">
|
||||
<textarea
|
||||
v-model="replyDrafts[comment.id]"
|
||||
:ref="(element) => registerReplyInput(comment.id, element)"
|
||||
class="commentsComposer__input commentsComposer__input--reply"
|
||||
maxlength="2000"
|
||||
rows="2"
|
||||
@@ -261,7 +291,7 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
<div class="commentsComposer__footer">
|
||||
<span class="commentsComposer__hint">{{ (replyDrafts[comment.id] || '').length }}/2000</span>
|
||||
<button class="btn btn--save btn--small" type="button" :disabled="!(replyDrafts[comment.id] || '').trim() || submittingTargetId === comment.id" @click="submitComment(comment.id)">
|
||||
<button class="btn btn--save commentsComposer__submit" type="button" :disabled="!(replyDrafts[comment.id] || '').trim() || submittingTargetId === comment.id" @click="submitComment(comment.id)">
|
||||
답글 등록
|
||||
</button>
|
||||
</div>
|
||||
@@ -280,7 +310,10 @@ onBeforeUnmount(() => {
|
||||
<img v-if="avatarUrlOf(reply)" class="commentItem__avatar" :src="avatarUrlOf(reply)" :alt="displayNameOf(reply)" draggable="false" />
|
||||
<div v-else class="commentItem__avatar commentItem__avatar--fallback">{{ avatarFallbackOf(reply) }}</div>
|
||||
<div class="commentItem__meta">
|
||||
<div class="commentItem__name">{{ displayNameOf(reply) }}</div>
|
||||
<div class="commentItem__metaTop">
|
||||
<div class="commentItem__name">{{ displayNameOf(reply) }}</div>
|
||||
<span class="commentItem__badge commentItem__badge--reply">답글</span>
|
||||
</div>
|
||||
<div class="commentItem__date">{{ formatDate(reply.createdAt) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -385,10 +418,20 @@ onBeforeUnmount(() => {
|
||||
background: var(--theme-input-bg);
|
||||
color: var(--theme-text);
|
||||
box-sizing: border-box;
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--theme-field-border) 45%, transparent);
|
||||
}
|
||||
|
||||
.commentsComposer__input--reply {
|
||||
min-height: 72px;
|
||||
background: color-mix(in srgb, var(--theme-input-bg) 82%, var(--theme-surface-soft));
|
||||
}
|
||||
|
||||
.commentsComposer__input:focus {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--theme-accent) 60%, var(--theme-field-border));
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--theme-accent) 52%, transparent),
|
||||
0 0 0 3px color-mix(in srgb, var(--theme-accent) 16%, transparent);
|
||||
}
|
||||
|
||||
.commentsComposer__footer,
|
||||
@@ -416,18 +459,36 @@ onBeforeUnmount(() => {
|
||||
|
||||
.commentsThread {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.commentItem {
|
||||
padding: 16px;
|
||||
border-radius: 22px;
|
||||
position: relative;
|
||||
padding: 18px 18px 16px;
|
||||
border-radius: 24px;
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--theme-surface) 88%, var(--theme-surface-soft)) 0%, var(--theme-surface) 100%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 color-mix(in srgb, white 6%, transparent),
|
||||
0 14px 32px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.commentItem--reply {
|
||||
margin-top: 12px;
|
||||
margin-left: 24px;
|
||||
border-radius: 18px;
|
||||
margin-left: 28px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--theme-accent) 6%, var(--theme-surface)) 0%, var(--theme-surface) 100%);
|
||||
}
|
||||
|
||||
.commentItem--reply::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
left: -16px;
|
||||
width: 12px;
|
||||
height: calc(100% - 44px);
|
||||
border-left: 2px solid color-mix(in srgb, var(--theme-accent) 36%, var(--theme-card-border));
|
||||
border-bottom: 2px solid color-mix(in srgb, var(--theme-accent) 36%, var(--theme-card-border));
|
||||
border-bottom-left-radius: 14px;
|
||||
}
|
||||
|
||||
.commentItem--highlighted {
|
||||
@@ -470,11 +531,37 @@ onBeforeUnmount(() => {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.commentItem__metaTop {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.commentItem__name {
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.commentItem__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
padding: 0 9px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--theme-surface-soft) 84%, transparent);
|
||||
color: var(--theme-text-muted);
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.commentItem__badge--reply {
|
||||
background: color-mix(in srgb, var(--theme-accent) 15%, var(--theme-surface-soft));
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.commentItem__date {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
@@ -501,6 +588,10 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.commentItem__body {
|
||||
padding: 14px 15px;
|
||||
border-radius: 18px;
|
||||
background: color-mix(in srgb, var(--theme-input-bg) 82%, var(--theme-surface));
|
||||
border: 1px solid color-mix(in srgb, var(--theme-card-border) 76%, transparent);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
line-height: 1.6;
|
||||
@@ -508,6 +599,15 @@ onBeforeUnmount(() => {
|
||||
|
||||
.replyComposer {
|
||||
margin-top: 14px;
|
||||
padding: 14px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid var(--theme-card-border);
|
||||
background: color-mix(in srgb, var(--theme-surface) 76%, var(--theme-surface-soft));
|
||||
}
|
||||
|
||||
.commentsComposer__submit {
|
||||
min-width: 112px;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
@@ -524,7 +624,12 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.commentItem--reply {
|
||||
margin-left: 12px;
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.commentItem--reply::before {
|
||||
left: -10px;
|
||||
width: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,6 +20,10 @@ function avatarUrlOf(notification) {
|
||||
return notification.actorAvatarSrc ? toApiUrl(notification.actorAvatarSrc) : ''
|
||||
}
|
||||
|
||||
function tierListThumbnailUrl(notification) {
|
||||
return notification.tierListThumbnailSrc ? toApiUrl(notification.tierListThumbnailSrc) : ''
|
||||
}
|
||||
|
||||
function avatarFallbackOf(notification) {
|
||||
return displayInitialFrom(notification.actorName, notification.actorAccountName, '?')
|
||||
}
|
||||
@@ -38,6 +42,10 @@ function notificationTitle(notification) {
|
||||
return notification.notificationType === 'comment_reply' ? '내 댓글에 답글이 달렸어요.' : '내 티어표에 새 댓글이 달렸어요.'
|
||||
}
|
||||
|
||||
function notificationLead(notification) {
|
||||
return notification.notificationType === 'comment_reply' ? '원래 댓글과 새 답글을 함께 확인해보세요.' : '내 티어표에 새로 남겨진 댓글입니다.'
|
||||
}
|
||||
|
||||
function emitUnreadCount(unread) {
|
||||
if (typeof window === 'undefined') return
|
||||
window.dispatchEvent(new CustomEvent('tier-maker:comment-inbox-updated', { detail: { unreadCount: unread } }))
|
||||
@@ -145,10 +153,26 @@ watch(unreadOnly, loadInbox)
|
||||
:class="{ 'commentInboxCard--unread': !notification.isRead }"
|
||||
>
|
||||
<button class="commentInboxCard__body" type="button" @click="openNotification(notification)">
|
||||
<div class="commentInboxCard__thumbWrap">
|
||||
<img
|
||||
v-if="tierListThumbnailUrl(notification)"
|
||||
class="commentInboxCard__thumb"
|
||||
:src="tierListThumbnailUrl(notification)"
|
||||
:alt="notification.tierListTitle || '티어표 썸네일'"
|
||||
draggable="false"
|
||||
/>
|
||||
<div v-else class="commentInboxCard__thumbFallback">티어표</div>
|
||||
</div>
|
||||
<div class="commentInboxCard__main">
|
||||
<div class="commentInboxCard__titleRow">
|
||||
<div class="commentInboxCard__title">{{ notificationTitle(notification) }}</div>
|
||||
<span v-if="!notification.isRead" class="commentInboxCard__dot" aria-label="안 읽음"></span>
|
||||
<div>
|
||||
<div class="commentInboxCard__title">{{ notificationTitle(notification) }}</div>
|
||||
<div class="commentInboxCard__lead">{{ notificationLead(notification) }}</div>
|
||||
</div>
|
||||
<div class="commentInboxCard__status">
|
||||
<span v-if="!notification.isRead" class="commentInboxCard__dot" aria-label="안 읽음"></span>
|
||||
<span class="commentInboxCard__badge">{{ notification.notificationType === 'comment_reply' ? '답글' : '댓글' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="commentInboxCard__meta">
|
||||
<img
|
||||
@@ -167,7 +191,16 @@ watch(unreadOnly, loadInbox)
|
||||
{{ notification.tierListTitle || '제목 없는 티어표' }}
|
||||
<span class="commentInboxCard__targetMeta">/ {{ notification.topicName || notification.topicSlug || notification.topicId }}</span>
|
||||
</div>
|
||||
<div class="commentInboxCard__content">{{ notification.commentContent }}</div>
|
||||
<div class="commentInboxCard__thread">
|
||||
<div v-if="notification.parentCommentContent" class="commentInboxCard__threadBlock">
|
||||
<div class="commentInboxCard__threadLabel">원래 댓글</div>
|
||||
<div class="commentInboxCard__threadText">{{ notification.parentCommentContent }}</div>
|
||||
</div>
|
||||
<div class="commentInboxCard__threadBlock commentInboxCard__threadBlock--accent">
|
||||
<div class="commentInboxCard__threadLabel">{{ notification.notificationType === 'comment_reply' ? '새 답글' : '새 댓글' }}</div>
|
||||
<div class="commentInboxCard__threadText">{{ notification.commentContent }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</article>
|
||||
@@ -230,20 +263,64 @@ watch(unreadOnly, loadInbox)
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 140px minmax(0, 1fr);
|
||||
gap: 18px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.commentInboxCard__thumbWrap {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 10;
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
background: var(--theme-thumb-fallback-bg);
|
||||
border: 1px solid color-mix(in srgb, var(--theme-card-border) 78%, transparent);
|
||||
}
|
||||
|
||||
.commentInboxCard__thumb,
|
||||
.commentInboxCard__thumbFallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.commentInboxCard__thumb {
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.commentInboxCard__thumbFallback {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--theme-text-faint);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.commentInboxCard__titleRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.commentInboxCard__title {
|
||||
font-size: 18px;
|
||||
font-size: 17px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.commentInboxCard__lead {
|
||||
margin-top: 6px;
|
||||
color: var(--theme-text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.commentInboxCard__status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.commentInboxCard__dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
@@ -252,6 +329,18 @@ watch(unreadOnly, loadInbox)
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.commentInboxCard__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--theme-accent) 18%, var(--theme-surface-soft));
|
||||
color: var(--theme-text);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.commentInboxCard__meta {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
@@ -291,13 +380,40 @@ watch(unreadOnly, loadInbox)
|
||||
|
||||
.commentInboxCard__content {
|
||||
margin-top: 10px;
|
||||
color: var(--theme-text-muted);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.commentInboxCard__thread {
|
||||
margin-top: 14px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.commentInboxCard__threadBlock {
|
||||
padding: 14px 15px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid var(--theme-card-border);
|
||||
background: color-mix(in srgb, var(--theme-surface) 88%, var(--theme-surface-soft));
|
||||
}
|
||||
|
||||
.commentInboxCard__threadBlock--accent {
|
||||
border-color: color-mix(in srgb, var(--theme-accent) 38%, var(--theme-card-border));
|
||||
background: color-mix(in srgb, var(--theme-accent) 10%, var(--theme-surface));
|
||||
}
|
||||
|
||||
.commentInboxCard__threadLabel {
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--theme-text-faint);
|
||||
}
|
||||
|
||||
.commentInboxCard__threadText {
|
||||
margin-top: 8px;
|
||||
color: var(--theme-text);
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
@@ -309,5 +425,9 @@ watch(unreadOnly, loadInbox)
|
||||
.commentInboxPanel {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.commentInboxCard__body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user