Files
BRETT/app/components/CommentsSidebar.vue
T
FliesentischKingandDevin 456776ea68 Shorten thread route from /thread to /t
All internal links (tiles, prev/next nav, comments, notifications,
breadcrumb, dropzone route check) point to /t/<id>. A global route
middleware permanently redirects old /thread/<id> URLs including
query string and comment anchor.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-04 15:24:32 +02:00

99 lines
3.6 KiB
Vue

<script setup lang="ts">
import {
Sidebar,
SidebarContent,
} from '@/components/ui/sidebar'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import type { CommentAttachment } from '~~/shared/utils/attachments'
interface LatestComment {
id: number
content: string
createdAt: string
userName: string
userId: number
userColor: string | null
userAvatarUrl: string | null
uploadId: string
attachments?: CommentAttachment[]
}
const { data: comments } = await useFetch<LatestComment[]>('/api/comments/latest', {
key: 'latest-comments',
default: () => [],
})
const { liveComments } = useLiveComments()
const timeAgo = useTimeAgo()
const mergedComments = computed(() => {
const seen = new Set<number>()
return [...liveComments.value, ...comments.value]
.filter((c) => {
if (seen.has(c.id)) return false
seen.add(c.id)
return true
})
.slice(0, 15)
})
</script>
<template>
<Sidebar side="right" collapsible="none" class="sticky top-0 hidden h-svh shrink-0 overflow-hidden rounded-l-md border-l lg:flex">
<SidebarContent class="px-2 py-2">
<p v-if="!mergedComments.length" class="px-2 py-1 text-sm text-muted-foreground">
{{ $t('comments.none') }}
</p>
<NuxtLink
v-for="comment in mergedComments"
:key="comment.id"
:to="`/t/${comment.uploadId}#comment-${comment.id}`"
class="block rounded-md border border-sidebar-border/60 bg-sidebar px-3 py-2.5 text-sm transition-colors hover:border-sidebar-border hover:bg-sidebar-accent"
>
<div class="flex items-center gap-2 text-xs text-muted-foreground">
<NuxtImg
v-if="comment.userAvatarUrl"
:src="comment.userAvatarUrl"
:alt="comment.userName"
width="28"
height="28"
fit="cover"
class="size-7 shrink-0 rounded-sm"
/>
<span
v-else
class="flex size-7 shrink-0 items-center justify-center rounded-sm text-xs font-medium"
:style="comment.userColor ? { backgroundColor: comment.userColor, color: '#fff' } : { backgroundColor: 'var(--color-muted)' }"
>{{ comment.userName.slice(0, 2).toUpperCase() }}</span>
<UserHoverCard
:user-id="comment.userId"
:user-name="comment.userName"
:user-color="comment.userColor"
:user-avatar-url="comment.userAvatarUrl"
>
<span
class="truncate text-sm font-medium text-foreground hover:underline"
:style="comment.userColor ? { color: comment.userColor } : undefined"
@click.stop.prevent="navigateTo(`/user/${comment.userId}`)"
>{{ comment.userName }}</span>
</UserHoverCard>
<span class="shrink-0">· {{ timeAgo(comment.createdAt) }}</span>
</div>
<HoverCard :open-delay="250" :close-delay="100">
<HoverCardTrigger as-child>
<div class="mt-1.5 line-clamp-3 break-words">
<CommentContent :content="comment.content" compact /><CommentAttachments v-if="comment.attachments?.length" :attachments="comment.attachments" compact />
</div>
</HoverCardTrigger>
<HoverCardContent side="left" align="start" class="w-80 max-w-[90vw] text-sm">
<div class="max-h-64 overflow-y-auto break-words">
<CommentContent :content="comment.content" />
<CommentAttachments v-if="comment.attachments?.length" :attachments="comment.attachments" />
</div>
</HoverCardContent>
</HoverCard>
</NuxtLink>
</SidebarContent>
</Sidebar>
</template>