153 lines
4.9 KiB
Text
153 lines
4.9 KiB
Text
|
|
---
|
|||
|
|
import { MONTHS } from "@lib/constants";
|
|||
|
|
|
|||
|
|
const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || "http://localhost:8090";
|
|||
|
|
|
|||
|
|
export interface Props {
|
|||
|
|
currentPostId?: string;
|
|||
|
|
currentPostSlug?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const { currentPostId, currentPostSlug } = Astro.props;
|
|||
|
|
|
|||
|
|
interface PostRecord {
|
|||
|
|
id: string;
|
|||
|
|
title: string;
|
|||
|
|
slug: string;
|
|||
|
|
excerpt: string;
|
|||
|
|
image?: string;
|
|||
|
|
tags?: string;
|
|||
|
|
created: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface PocketBaseResponse {
|
|||
|
|
items: PostRecord[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Получаем похожие посты (исключая текущий)
|
|||
|
|
let relatedPosts: PostRecord[] = [];
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const filter = currentPostId ? `id!="${currentPostId}"` : "";
|
|||
|
|
const response = await fetch(
|
|||
|
|
`${POCKETBASE_URL}/api/collections/posts/records?filter=${encodeURIComponent(filter)}&perPage=3`
|
|||
|
|
);
|
|||
|
|
const data: PocketBaseResponse = await response.json();
|
|||
|
|
|
|||
|
|
if (data.items) {
|
|||
|
|
relatedPosts = data.items;
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error("Error fetching related posts:", error);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Форматируем дату
|
|||
|
|
function formatDate(dateString: string) {
|
|||
|
|
const date = new Date(dateString);
|
|||
|
|
return `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Парсим tags
|
|||
|
|
function parseTags(tagsString?: string): string {
|
|||
|
|
if (!tagsString) return "НОВОСТИ";
|
|||
|
|
try {
|
|||
|
|
const tags = typeof tagsString === "string" ? JSON.parse(tagsString) : tagsString;
|
|||
|
|
return Array.isArray(tags) && tags.length > 0 ? tags[0].toUpperCase() : "НОВОСТИ";
|
|||
|
|
} catch {
|
|||
|
|
return "НОВОСТИ";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
<section
|
|||
|
|
class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 border-t border-gray-200"
|
|||
|
|
>
|
|||
|
|
{/* Заголовок */}
|
|||
|
|
<div class="flex items-center justify-between mb-8">
|
|||
|
|
<h2 class="text-2xl font-bold text-gray-900">Похожие публикации</h2>
|
|||
|
|
<a
|
|||
|
|
href="/blog"
|
|||
|
|
class="text-blue-600 hover:text-blue-800 font-medium text-sm flex items-center gap-1 transition-colors group"
|
|||
|
|
>
|
|||
|
|
Все статьи
|
|||
|
|
<svg
|
|||
|
|
class="w-4 h-4 transition-transform group-hover:translate-x-1"
|
|||
|
|
fill="none"
|
|||
|
|
stroke="currentColor"
|
|||
|
|
viewBox="0 0 24 24"
|
|||
|
|
>
|
|||
|
|
<path
|
|||
|
|
stroke-linecap="round"
|
|||
|
|
stroke-linejoin="round"
|
|||
|
|
stroke-width="2"
|
|||
|
|
d="M9 5l7 7-7 7"></path>
|
|||
|
|
</svg>
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Сетка карточек */}
|
|||
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|||
|
|
{relatedPosts && relatedPosts.length > 0 ? (
|
|||
|
|
relatedPosts.map((post) => {
|
|||
|
|
const imageUrl = post.image
|
|||
|
|
? `${POCKETBASE_URL}/api/files/posts/${post.id}/${post.image}`
|
|||
|
|
: "https://images.unsplash.com/photo-1589829085413-56de8ae18c73?q=80&w=800&auto=format&fit=crop";
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<article class="group bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-100 hover:shadow-lg transition-all duration-300">
|
|||
|
|
{/* Изображение */}
|
|||
|
|
<a
|
|||
|
|
href={`/blog/${post.slug}`}
|
|||
|
|
class="block relative overflow-hidden aspect-video"
|
|||
|
|
>
|
|||
|
|
<img
|
|||
|
|
src={imageUrl}
|
|||
|
|
alt={post.title}
|
|||
|
|
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
|||
|
|
/>
|
|||
|
|
<div class="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
|||
|
|
</a>
|
|||
|
|
|
|||
|
|
{/* Контент */}
|
|||
|
|
<div class="p-6">
|
|||
|
|
<span class="inline-block px-3 py-1 bg-blue-50 text-blue-600 text-xs font-medium rounded-full mb-3">
|
|||
|
|
{parseTags(post.tags)}
|
|||
|
|
</span>
|
|||
|
|
|
|||
|
|
<h3 class="text-lg font-bold text-gray-900 mb-3 line-clamp-2 group-hover:text-blue-600 transition-colors">
|
|||
|
|
<a href={`/blog/${post.slug}`}>{post.title}</a>
|
|||
|
|
</h3>
|
|||
|
|
|
|||
|
|
<p class="text-gray-600 text-sm line-clamp-3 mb-4 leading-relaxed">
|
|||
|
|
{post.excerpt}
|
|||
|
|
</p>
|
|||
|
|
|
|||
|
|
<a
|
|||
|
|
href={`/blog/${post.slug}`}
|
|||
|
|
class="inline-flex items-center gap-2 text-blue-600 font-medium text-sm hover:text-blue-800 transition-colors group/link"
|
|||
|
|
>
|
|||
|
|
Читать далее
|
|||
|
|
<svg
|
|||
|
|
class="w-4 h-4 transition-transform group-hover/link:translate-x-1"
|
|||
|
|
fill="none"
|
|||
|
|
stroke="currentColor"
|
|||
|
|
viewBox="0 0 24 24"
|
|||
|
|
>
|
|||
|
|
<path
|
|||
|
|
stroke-linecap="round"
|
|||
|
|
stroke-linejoin="round"
|
|||
|
|
stroke-width="2"
|
|||
|
|
d="M17 8l4 4m0 0l-4 4m4-4H3"
|
|||
|
|
/>
|
|||
|
|
</svg>
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
</article>
|
|||
|
|
);
|
|||
|
|
})
|
|||
|
|
) : (
|
|||
|
|
<p class="col-span-full text-center text-gray-500 py-8">Нет связанных публикаций</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</section>
|