first commit
This commit is contained in:
commit
4a589825c2
297 changed files with 33019 additions and 0 deletions
152
frontend/src/components/single-post/RelatedPosts.astro
Normal file
152
frontend/src/components/single-post/RelatedPosts.astro
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
---
|
||||
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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue