2026-04-09 22:22:55 +05:00
|
|
|
---
|
|
|
|
|
import ArticleLayout from '@layouts/ArticleLayout.astro';
|
|
|
|
|
import { SITE_URL } from '@constants';
|
2026-04-18 21:07:30 +05:00
|
|
|
import Comments from '@components/blog/comments/Comments.tsx';
|
2026-04-09 22:22:55 +05:00
|
|
|
import RelatedPosts from '@components/blog/RelatedPosts.astro';
|
2026-04-10 00:48:20 +05:00
|
|
|
import ArticleTableOfContents from '@components/blog/ArticleTableOfContents.astro';
|
2026-04-26 23:47:23 +05:00
|
|
|
import { getPostBySlug, getPosts, getPostImageUrl, getPostVotesStats, getPostViews } from '@lib/pb';
|
2026-04-15 02:12:25 +05:00
|
|
|
import { marked } from 'marked';
|
2026-04-09 22:22:55 +05:00
|
|
|
|
2026-04-15 02:12:25 +05:00
|
|
|
export const prerender = false;
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
|
|
|
const slug = Astro.params.slug;
|
|
|
|
|
|
|
|
|
|
if (!slug) {
|
|
|
|
|
return Astro.redirect('/blog');
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 02:12:25 +05:00
|
|
|
const post = await getPostBySlug(slug);
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
|
|
|
if (!post) {
|
|
|
|
|
return Astro.redirect('/blog');
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 17:35:17 +05:00
|
|
|
const { likes = 0, dislikes = 0 } = await getPostVotesStats(post.id).catch(() => ({ likes: 0, dislikes: 0 }));
|
2026-04-26 23:47:23 +05:00
|
|
|
const views = await getPostViews(post.id).catch(() => 0);
|
2026-04-17 17:35:17 +05:00
|
|
|
|
2026-04-15 02:12:25 +05:00
|
|
|
// Конвертируем markdown в HTML
|
|
|
|
|
const contentHtml = marked(post.content || '');
|
2026-04-09 22:22:55 +05:00
|
|
|
|
2026-04-26 22:45:19 +05:00
|
|
|
// Извлекаем заголовки для оглавления (поддержка HTML и Markdown)
|
|
|
|
|
const headingRegex = /<h([2-3])[^>]*>([^<]+)<\/?h[2-3]>/gi;
|
2026-04-10 00:48:20 +05:00
|
|
|
const tocItems: { id: string; text: string; level: number }[] = [];
|
2026-04-15 02:12:25 +05:00
|
|
|
const body = post.content || '';
|
2026-04-10 00:48:20 +05:00
|
|
|
let match;
|
|
|
|
|
let headingIndex = 0;
|
|
|
|
|
while ((match = headingRegex.exec(body)) !== null) {
|
2026-04-26 22:45:19 +05:00
|
|
|
const level = parseInt(match[1]); // Теперь 2 или 3
|
2026-04-10 00:48:20 +05:00
|
|
|
const text = match[2].trim();
|
|
|
|
|
tocItems.push({ level, id: `heading-${headingIndex++}`, text });
|
|
|
|
|
}
|
2026-04-15 02:12:25 +05:00
|
|
|
const formatDate = (date: string) => {
|
2026-04-26 18:09:40 +05:00
|
|
|
const d = new Date(date);
|
|
|
|
|
const day = d.getDate().toString().padStart(2, '0');
|
|
|
|
|
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
|
|
|
|
const year = new Date().getFullYear().toString().slice(-2);
|
|
|
|
|
return `${day}/${month}/${year}`;
|
2026-04-09 22:22:55 +05:00
|
|
|
};
|
|
|
|
|
|
2026-04-15 02:12:25 +05:00
|
|
|
// Для related posts берем те же категории
|
|
|
|
|
const { posts: relatedPosts } = await getPosts({ perPage: 4, category: post.category });
|
|
|
|
|
const filteredRelated = relatedPosts.filter(p => p.slug !== slug).slice(0, 3);
|
2026-04-09 22:22:55 +05:00
|
|
|
|
2026-04-15 02:12:25 +05:00
|
|
|
const currentUrl = `${SITE_URL}/blog/${slug}`;
|
2026-04-15 12:36:58 +05:00
|
|
|
const heroImage = getPostImageUrl(post);
|
2026-04-09 22:22:55 +05:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
<ArticleLayout
|
2026-04-15 02:12:25 +05:00
|
|
|
title={`${post.title} — автоюрист в Сургуте`}
|
|
|
|
|
description={post.description}
|
|
|
|
|
canonicalLink={`${SITE_URL}/blog/${slug}`}
|
2026-04-09 22:22:55 +05:00
|
|
|
breadcrumbs={[
|
|
|
|
|
{ label: 'Главная', href: '/' },
|
|
|
|
|
{ label: 'Блог', href: '/blog' },
|
2026-04-15 02:12:25 +05:00
|
|
|
{ label: post.title }
|
2026-04-09 22:22:55 +05:00
|
|
|
]}
|
2026-04-15 12:36:58 +05:00
|
|
|
heroImage={heroImage}
|
2026-04-15 02:12:25 +05:00
|
|
|
heroAlt={post.title}
|
|
|
|
|
category={post.category}
|
|
|
|
|
postTitle={post.title}
|
|
|
|
|
date={formatDate(post.date)}
|
|
|
|
|
author={post.author}
|
|
|
|
|
readTime={post.readTime}
|
2026-04-18 18:25:10 +05:00
|
|
|
readmeTime={post.readmeTime}
|
2026-04-09 22:22:55 +05:00
|
|
|
postId={post.id}
|
|
|
|
|
postUrl={currentUrl}
|
2026-04-17 17:35:17 +05:00
|
|
|
initialLikes={likes}
|
|
|
|
|
initialDislikes={dislikes}
|
2026-04-26 23:47:23 +05:00
|
|
|
initialViews={views}
|
2026-04-09 22:22:55 +05:00
|
|
|
>
|
2026-04-26 18:09:40 +05:00
|
|
|
<!-- Содержимое статьи в article -->
|
|
|
|
|
<article class="article-content-wrapper" id="post-content">
|
|
|
|
|
<div class="post-content" set:html={contentHtml} />
|
|
|
|
|
</article>
|
|
|
|
|
|
2026-04-28 01:58:37 +05:00
|
|
|
<!-- Комментарии и похожие статьи -->
|
2026-04-26 18:09:40 +05:00
|
|
|
<div class="comments-wrapper">
|
2026-04-18 21:07:30 +05:00
|
|
|
<Comments postSlug={post.slug} client:load />
|
2026-04-26 18:09:40 +05:00
|
|
|
</div>
|
2026-04-09 22:22:55 +05:00
|
|
|
|
2026-04-26 18:09:40 +05:00
|
|
|
<RelatedPosts posts={filteredRelated} currentSlug={slug} />
|
2026-04-10 00:48:20 +05:00
|
|
|
|
2026-04-26 22:45:19 +05:00
|
|
|
<!-- Оглавление -->
|
2026-04-26 18:09:40 +05:00
|
|
|
<div slot="sidebar">
|
|
|
|
|
<ArticleTableOfContents items={tocItems} />
|
|
|
|
|
</div>
|
2026-04-09 22:22:55 +05:00
|
|
|
</ArticleLayout>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
/* Post Content */
|
|
|
|
|
.post-content {
|
2026-04-10 00:48:20 +05:00
|
|
|
padding: 0;
|
2026-04-09 22:22:55 +05:00
|
|
|
color: #334155;
|
2026-04-26 18:09:40 +05:00
|
|
|
line-height: 1.85;
|
|
|
|
|
font-family: 'Merriweather', Georgia, serif;
|
2026-04-09 22:22:55 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.post-content :global(h2) {
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
font-size: 1.75rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
margin: 2rem 0 1rem;
|
|
|
|
|
letter-spacing: -0.02em;
|
2026-04-26 18:09:40 +05:00
|
|
|
font-family: 'Merriweather', Georgia, serif;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
gap: 0.6rem;
|
2026-04-09 22:22:55 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.post-content :global(h3) {
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
font-size: 1.375rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
margin: 1.75rem 0 1rem;
|
2026-04-26 18:09:40 +05:00
|
|
|
font-family: 'Merriweather', Georgia, serif;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
gap: 0.5rem;
|
2026-04-09 22:22:55 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.post-content :global(h2) {
|
2026-04-28 19:37:59 +05:00
|
|
|
font-size: 1rem;
|
|
|
|
|
text-align: center;
|
|
|
|
|
justify-content: center;
|
2026-04-09 22:22:55 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.post-content :global(h3) {
|
2026-04-28 19:37:59 +05:00
|
|
|
font-size: 0.9rem;
|
|
|
|
|
text-align: center;
|
|
|
|
|
justify-content: center;
|
2026-04-26 18:09:40 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.post-content :global(p) {
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.post-content :global(li) {
|
|
|
|
|
font-size: 0.9rem;
|
2026-04-09 22:22:55 +05:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-15 02:12:25 +05:00
|
|
|
</style>
|