Создан компонент поста

This commit is contained in:
Web-serfer 2026-04-09 22:22:55 +05:00
parent 674ef7fe04
commit d0f41672d1
32 changed files with 2082 additions and 289 deletions

View file

@ -0,0 +1,168 @@
---
import ArticleLayout from '@layouts/ArticleLayout.astro';
import { SITE_URL } from '@constants';
import PostCommentForm from '@components/blog/PostCommentForm.astro';
import RelatedPosts from '@components/blog/RelatedPosts.astro';
import { getCollection, getEntry, render } from 'astro:content';
export const prerender = false;
export async function getStaticPaths() {
const posts = await getCollection('blog') as { id: string; data: Record<string, any> }[];
return posts.map((post: { id: string }) => ({
params: { slug: post.id },
}));
}
const slug = Astro.params.slug;
if (!slug) {
return Astro.redirect('/blog');
}
const post = await getEntry('blog', slug);
if (!post) {
return Astro.redirect('/blog');
}
const { Content } = await render(post);
// Форматируем дату
const formatDate = (date: Date) => {
return date.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
};
// Логика авторизации (пока статичная переменная)
const isAuthorized = false;
// URL текущей страницы
const currentUrl = `${SITE_URL}/blog/${post.id}`;
// Получаем все посты для блока "Читайте также"
const allPosts = await getCollection('blog');
---
<ArticleLayout
title={`${post.data.title} — автоюрист в Сургуте`}
description={post.data.description}
canonicalLink={`${SITE_URL}/blog/${post.id}`}
breadcrumbs={[
{ label: 'Главная', href: '/' },
{ label: 'Блог', href: '/blog' },
{ label: post.data.title }
]}
heroImage={post.data.imageUrl}
heroAlt={post.data.title}
category={post.data.category}
postTitle={post.data.title}
date={formatDate(post.data.date)}
author={post.data.author}
readTime={post.data.readTime}
postId={post.id}
postUrl={currentUrl}
initialLikes={12}
initialDislikes={2}
>
<!-- Содержимое статьи -->
<div class="post-content">
<Content />
</div>
<!-- Форма комментариев -->
<PostCommentForm
postId={post.id}
isAuthorized={isAuthorized}
/>
<!-- Похожие статьи -->
<RelatedPosts
posts={allPosts}
currentSlug={post.id}
/>
</ArticleLayout>
<style>
/* Post Content */
.post-content {
padding: 3rem;
color: #334155;
line-height: 1.8;
max-width: 1200px;
margin: 0 auto;
}
.post-content :global(h2) {
color: #1e293b;
font-size: 1.75rem;
font-weight: 700;
margin: 2rem 0 1rem;
letter-spacing: -0.02em;
}
.post-content :global(h3) {
color: #1e293b;
font-size: 1.375rem;
font-weight: 700;
margin: 1.75rem 0 1rem;
}
.post-content :global(p) {
margin: 0 0 1.25rem;
font-size: 1.05rem;
}
.post-content :global(ul), .post-content :global(ol) {
margin: 1rem 0;
padding-left: 1.5rem;
}
.post-content :global(li) {
margin: 0.5rem 0;
font-size: 1.05rem;
}
.post-content :global(blockquote) {
margin: 2rem 0;
padding: 1.5rem 2rem;
background: #f8fafc;
border-left: 4px solid #d4af37;
border-radius: 0 0.75rem 0.75rem 0;
font-style: italic;
}
.post-content :global(blockquote p) {
margin: 0;
color: #475569;
}
.post-content :global(strong) {
color: #1e293b;
font-weight: 700;
}
.post-content :global(code) {
background: #f1f5f9;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-size: 0.9em;
}
@media (max-width: 768px) {
.post-content {
padding: 2rem;
}
.post-content :global(h2) {
font-size: 1.5rem;
}
.post-content :global(h3) {
font-size: 1.25rem;
}
}
</style>

View file

@ -4,19 +4,35 @@ import { SITE_URL } from '@constants';
import PageHero from '@components/base/PageHero.astro';
import BlogCategories from '@components/blog/BlogCategories.astro';
import BlogCard from '@components/blog/BlogCard.astro';
import BlogPagination from '@components/blog/BlogPagination.astro';
import Pagination from '@components/base/Pagination.astro';
import CTA from '@components/base/CTA.astro';
import SearchModal from '@components/base/SearchModal.astro';
import { blogPosts, categories } from '@data/blogData';
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
// Сортируем посты по дате (новые сверху)
const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
const POSTS_PER_PAGE = 6;
const currentPage = 1;
const totalPages = Math.ceil(blogPosts.length / POSTS_PER_PAGE);
const totalPages = Math.ceil(sortedPosts.length / POSTS_PER_PAGE);
const startIndex = 0;
const endIndex = POSTS_PER_PAGE;
const paginatedPosts = blogPosts.slice(startIndex, endIndex);
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
// Форматируем дату
const formatDate = (date: Date) => {
return date.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
};
// Категории
const categories = ['Все', ...new Set(posts.map((post: any) => post.data.category))];
---
<Layout
@ -49,17 +65,17 @@ const paginatedPosts = blogPosts.slice(startIndex, endIndex);
<section class="blog-grid-section">
<div class="site-container">
<div class="blog-grid" id="blog-grid">
{paginatedPosts.map((post) => (
<article class="blog-card-wrapper" data-category={post.category}>
{paginatedPosts.map((post: any) => (
<article class="blog-card-wrapper" data-category={post.data.category}>
<BlogCard
title={post.title}
description={post.description}
category={post.category}
categoryColor={post.categoryColor}
date={post.date}
readTime={post.readTime}
imageUrl={post.imageUrl}
slug={post.slug}
title={post.data.title}
description={post.data.description}
category={post.data.category}
categoryColor={post.data.categoryColor}
date={formatDate(post.data.date)}
readTime={post.data.readTime}
imageUrl={post.data.imageUrl}
slug={`/blog/${post.id}`}
/>
</article>
))}

View file

@ -4,19 +4,35 @@ import { SITE_URL } from '@constants';
import PageHero from '@components/base/PageHero.astro';
import BlogCategories from '@components/blog/BlogCategories.astro';
import BlogCard from '@components/blog/BlogCard.astro';
import BlogPagination from '@components/blog/BlogPagination.astro';
import Pagination from '@components/base/Pagination.astro';
import CTA from '@components/base/CTA.astro';
import SearchModal from '@components/base/SearchModal.astro';
import { blogPosts, categories } from '@data/blogData';
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
// Сортируем посты по дате (новые сверху)
const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
const POSTS_PER_PAGE = 6;
const currentPage = Number(Astro.params.page) || 1;
const totalPages = Math.ceil(blogPosts.length / POSTS_PER_PAGE);
const totalPages = Math.ceil(sortedPosts.length / POSTS_PER_PAGE);
const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
const endIndex = startIndex + POSTS_PER_PAGE;
const paginatedPosts = blogPosts.slice(startIndex, endIndex);
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
// Форматируем дату
const formatDate = (date: Date) => {
return date.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
};
// Категории
const categories = ['Все', ...new Set(posts.map(post => post.data.category))];
---
<Layout
@ -50,17 +66,17 @@ const paginatedPosts = blogPosts.slice(startIndex, endIndex);
<section class="blog-grid-section">
<div class="site-container">
<div class="blog-grid" id="blog-grid">
{paginatedPosts.map((post) => (
<article class="blog-card-wrapper" data-category={post.category}>
{paginatedPosts.map((post: any) => (
<article class="blog-card-wrapper" data-category={post.data.category}>
<BlogCard
title={post.title}
description={post.description}
category={post.category}
categoryColor={post.categoryColor}
date={post.date}
readTime={post.readTime}
imageUrl={post.imageUrl}
slug={post.slug}
title={post.data.title}
description={post.data.description}
category={post.data.category}
categoryColor={post.data.categoryColor}
date={formatDate(post.data.date)}
readTime={post.data.readTime}
imageUrl={post.data.imageUrl}
slug={`/blog/${post.id}`}
/>
</article>
))}

View file

@ -3,7 +3,9 @@ import Layout from '@layouts/Layout.astro';
import { SITE_URL } from '@constants';
import BlogCard from '@components/blog/BlogCard.astro';
import SearchModal from '@components/base/SearchModal.astro';
import { blogPosts } from '@data/blogData';
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
// Получаем параметр поиска из URL
const url = new URL(Astro.request.url);
@ -16,21 +18,30 @@ const breadcrumbsItems = [
];
// Функция поиска по статьям
function searchArticles(query: string) {
function searchArticles(query: string, allPosts: typeof posts) {
if (!query.trim()) return [];
const lowerQuery = query.toLowerCase();
return blogPosts.filter(post => {
const titleMatch = post.title.toLowerCase().includes(lowerQuery);
const descriptionMatch = post.description.toLowerCase().includes(lowerQuery);
const categoryMatch = post.category.toLowerCase().includes(lowerQuery);
return allPosts.filter(post => {
const titleMatch = post.data.title.toLowerCase().includes(lowerQuery);
const descriptionMatch = post.data.description.toLowerCase().includes(lowerQuery);
const categoryMatch = post.data.category.toLowerCase().includes(lowerQuery);
return titleMatch || descriptionMatch || categoryMatch;
});
}
const searchResults = searchArticles(searchQuery);
const searchResults = searchArticles(searchQuery, posts);
// Форматируем дату
const formatDate = (date: Date) => {
return date.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
};
---
<Layout
@ -46,20 +57,20 @@ const searchResults = searchArticles(searchQuery);
<h1 class="search-title">
{searchQuery ? `Результаты поиска` : 'Поиск статей'}
</h1>
{searchQuery && (
<div class="search-info">
<p class="search-query-text">
По запросу: <span class="query-highlight">"{searchQuery}"</span>
</p>
<p class="search-count">
{searchResults.length === 0
? 'Ничего не найдено'
{searchResults.length === 0
? 'Ничего не найдено'
: `Найдено статей: ${searchResults.length}`}
</p>
</div>
)}
<button class="open-search-btn" id="open-search-btn" data-modal-target="search-modal">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
@ -76,16 +87,16 @@ const searchResults = searchArticles(searchQuery);
<section class="results-section">
<div class="site-container">
<div class="results-grid">
{searchResults.map((post) => (
{searchResults.map((post: any) => (
<BlogCard
title={post.title}
description={post.description}
category={post.category}
categoryColor={post.categoryColor}
date={post.date}
readTime={post.readTime}
imageUrl={post.imageUrl}
slug={post.slug}
title={post.data.title}
description={post.data.description}
category={post.data.category}
categoryColor={post.data.categoryColor}
date={formatDate(post.data.date)}
readTime={post.data.readTime}
imageUrl={post.data.imageUrl}
slug={`/blog/${post.id}`}
/>
))}
</div>
@ -131,10 +142,10 @@ const searchResults = searchArticles(searchQuery);
<script>
(function() {
const btn = document.getElementById('open-search-btn');
btn?.addEventListener('click', () => {
window.dispatchEvent(new CustomEvent('open-modal', {
detail: 'search-modal'
window.dispatchEvent(new CustomEvent('open-modal', {
detail: 'search-modal'
}));
});
@ -142,11 +153,11 @@ const searchResults = searchArticles(searchQuery);
document.addEventListener('DOMContentLoaded', () => {
const urlParams = new URLSearchParams(window.location.search);
const query = urlParams.get('q');
if (!query && btn) {
setTimeout(() => {
window.dispatchEvent(new CustomEvent('open-modal', {
detail: 'search-modal'
window.dispatchEvent(new CustomEvent('open-modal', {
detail: 'search-modal'
}));
}, 300);
}