189 lines
5.4 KiB
Vue
189 lines
5.4 KiB
Vue
<template>
|
|
<UCard variant="outline" class="update-card">
|
|
<div class="flex gap-4">
|
|
<!-- Avatar -->
|
|
<div class="flex-shrink-0">
|
|
<img
|
|
v-if="update.author?.avatar"
|
|
:src="`/ghosties/Ghost-${capitalize(update.author.avatar)}.png`"
|
|
:alt="update.author.name"
|
|
class="w-12 h-12 rounded-full"
|
|
@error="handleImageError"
|
|
/>
|
|
<div
|
|
v-else
|
|
class="w-12 h-12 rounded-full bg-stone-700 flex items-center justify-center text-stone-300 font-bold"
|
|
>
|
|
{{ update.author?.name?.charAt(0)?.toUpperCase() || "?" }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="flex-1 min-w-0">
|
|
<!-- Header -->
|
|
<div class="flex items-start justify-between gap-4 mb-2">
|
|
<div>
|
|
<h3 class="font-semibold text-stone-100">
|
|
<NuxtLink
|
|
v-if="update.author?._id"
|
|
:to="`/updates/user/${update.author._id}`"
|
|
class="hover:text-stone-300 transition-colors"
|
|
>
|
|
{{ update.author.name }}
|
|
</NuxtLink>
|
|
<span v-else>Unknown Member</span>
|
|
</h3>
|
|
<div class="flex items-center gap-2 text-sm text-stone-400">
|
|
<time :datetime="update.createdAt">
|
|
{{ formatDate(update.createdAt) }}
|
|
</time>
|
|
<span v-if="isEdited" class="text-stone-500">(edited)</span>
|
|
<span
|
|
v-if="update.privacy === 'private'"
|
|
class="px-2 py-0.5 bg-stone-700 text-stone-300 rounded text-xs"
|
|
>
|
|
Private
|
|
</span>
|
|
<span
|
|
v-if="update.privacy === 'public'"
|
|
class="px-2 py-0.5 bg-stone-700 text-stone-300 rounded text-xs"
|
|
>
|
|
Public
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions (for author only) -->
|
|
<div v-if="isAuthor" class="flex gap-2">
|
|
<UButton
|
|
variant="ghost"
|
|
color="neutral"
|
|
size="xs"
|
|
icon="i-lucide-edit"
|
|
@click="$emit('edit', update)"
|
|
/>
|
|
<UButton
|
|
variant="ghost"
|
|
color="neutral"
|
|
size="xs"
|
|
icon="i-lucide-trash-2"
|
|
@click="$emit('delete', update)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="text-stone-200 whitespace-pre-wrap break-words mb-3">
|
|
<template v-if="showPreview && update.content.length > 300">
|
|
{{ update.content.substring(0, 300) }}...
|
|
<NuxtLink
|
|
:to="`/updates/${update._id}`"
|
|
class="text-stone-400 hover:text-stone-300 ml-1"
|
|
>
|
|
Read more
|
|
</NuxtLink>
|
|
</template>
|
|
<template v-else>
|
|
{{ update.content }}
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Images (if any) -->
|
|
<div v-if="update.images?.length" class="mb-3 space-y-2">
|
|
<img
|
|
v-for="(image, index) in update.images"
|
|
:key="index"
|
|
:src="image.url"
|
|
:alt="image.alt || 'Update image'"
|
|
class="rounded-lg max-w-full h-auto"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Footer actions -->
|
|
<div class="flex items-center gap-4 text-sm text-stone-400">
|
|
<NuxtLink
|
|
:to="`/updates/${update._id}`"
|
|
class="hover:text-stone-300 transition-colors"
|
|
>
|
|
View full update
|
|
</NuxtLink>
|
|
<span v-if="update.commentsEnabled" class="text-stone-500">
|
|
Comments (coming soon)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
</template>
|
|
|
|
<script setup>
|
|
const props = defineProps({
|
|
update: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
showPreview: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
});
|
|
|
|
defineEmits(["edit", "delete"]);
|
|
|
|
const { memberData } = useAuth();
|
|
|
|
const isAuthor = computed(() => {
|
|
return memberData.value && props.update.author?._id === memberData.value.id;
|
|
});
|
|
|
|
const isEdited = computed(() => {
|
|
const created = new Date(props.update.createdAt).getTime();
|
|
const updated = new Date(props.update.updatedAt).getTime();
|
|
return updated - created > 1000; // More than 1 second difference
|
|
});
|
|
|
|
const capitalize = (str) => {
|
|
if (!str) return "";
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
};
|
|
|
|
const handleImageError = (e) => {
|
|
e.target.src = "/ghosties/Ghost-Mild.png"; // Fallback ghost
|
|
};
|
|
|
|
const formatDate = (date) => {
|
|
const now = new Date();
|
|
const updateDate = new Date(date);
|
|
const diffInSeconds = Math.floor((now - updateDate) / 1000);
|
|
|
|
if (diffInSeconds < 60) return "just now";
|
|
if (diffInSeconds < 3600)
|
|
return `${Math.floor(diffInSeconds / 60)} minutes ago`;
|
|
if (diffInSeconds < 86400)
|
|
return `${Math.floor(diffInSeconds / 3600)} hours ago`;
|
|
if (diffInSeconds < 604800)
|
|
return `${Math.floor(diffInSeconds / 86400)} days ago`;
|
|
|
|
return updateDate.toLocaleDateString("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
year:
|
|
updateDate.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
|
|
});
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.update-card {
|
|
background-color: rgb(41 37 36);
|
|
border-color: rgb(87 83 78);
|
|
}
|
|
|
|
.update-card:hover {
|
|
border-color: rgb(120 113 108);
|
|
}
|
|
|
|
:deep(.card) {
|
|
background-color: rgb(41 37 36);
|
|
}
|
|
</style>
|