Правки в компонент поста

This commit is contained in:
Web-serfer 2026-04-10 00:48:20 +05:00
parent d0f41672d1
commit 9c5b9a1dbe
11 changed files with 405 additions and 316 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

View file

@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

Before

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View file

@ -0,0 +1,161 @@
---
interface TOCItem {
id: string;
text: string;
level: number;
}
interface Props {
items: TOCItem[];
}
const { items } = Astro.props;
---
{items.length > 0 && (
<aside class="toc-container" id="toc-sidebar">
<nav class="toc-nav">
<h3 class="toc-title">Оглавление</h3>
<ul class="toc-list">
{items.map((item) => (
<li class={`toc-item level-${item.level}`} data-toc-target={item.id}>
<a href={`#${item.id}`} class="toc-link">
<span class="toc-text">{item.text}</span>
</a>
</li>
))}
</ul>
</nav>
</aside>
)}
<style>
.toc-container { width: 100%; }
.toc-nav {
background: #ffffff;
border-radius: 1.25rem;
padding: 1.5rem;
border: 1px solid #e2e8f0;
box-shadow: 0 4px 20px rgba(0,0,0,0.03);
}
.toc-title {
color: #1e3050;
font-size: 1.1rem;
font-weight: 800;
margin: 0 0 1.25rem;
padding-bottom: 0.75rem;
border-bottom: 2px solid #eac26e;
}
.toc-list {
list-style: none;
padding: 0;
margin: 0;
counter-reset: toc-h2;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
/* ЧЕРНАЯ НУМЕРАЦИЯ */
.toc-item.level-2 { counter-reset: toc-h3; counter-increment: toc-h2; }
.toc-item.level-3 { counter-increment: toc-h3; padding-left: 1.25rem; }
.toc-link {
display: flex;
padding: 0.5rem 0.75rem;
color: #64748b;
text-decoration: none;
font-size: 0.9rem;
line-height: 1.4;
border-radius: 0.5rem;
transition: all 0.2s ease;
}
/* Номера в оглавлении */
.toc-link::before {
color: #1e3050;
font-weight: 700;
margin-right: 0.6rem;
flex-shrink: 0;
}
.level-2 .toc-link::before { content: counter(toc-h2) "."; }
.level-3 .toc-link::before { content: counter(toc-h2) "." counter(toc-h3) "."; font-size: 0.85em; opacity: 0.7; }
.toc-link:hover { color: #1e3050; background: #f1f5f9; }
/* АКТИВНЫЙ ПУНКТ (При скролле) */
.toc-item.active .toc-link {
color: #eac26e;
font-weight: 700;
background: rgba(234, 194, 110, 0.1);
}
.toc-item.active .toc-link::before { color: #eac26e; }
</style>
<script>
function setupTOC() {
const toc = document.getElementById('toc-sidebar');
if (!toc) return;
const items = toc.querySelectorAll('.toc-item');
const links = toc.querySelectorAll<HTMLAnchorElement>('.toc-link');
const HEADER_OFFSET = 120; // Высота хедера + запас
// --- 1. ПЛАВНЫЙ СКРОЛЛ ПРИ КЛИКЕ ---
links.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const id = link.getAttribute('href')?.substring(1);
const target = document.getElementById(id || '');
if (target) {
const elementPosition = target.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - HEADER_OFFSET;
window.scrollTo({
top: offsetPosition,
behavior: "smooth"
});
// Обновляем URL
history.pushState(null, '', `#${id}`);
}
});
});
// --- 2. ПОДСВЕТКА ПРИ СКРОЛЛЕ (SCROLL SPY) ---
const headingElements = Array.from(items)
.map(item => {
const id = item.getAttribute('data-toc-target');
return id ? document.getElementById(id) : null;
})
.filter((el): el is HTMLElement => el !== null);
const observerOptions = {
root: null,
rootMargin: '-120px 0px -70% 0px', // Зона срабатывания: верхняя часть экрана
threshold: 0
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const activeId = entry.target.id;
items.forEach(item => {
if (item.getAttribute('data-toc-target') === activeId) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
});
}, observerOptions);
headingElements.forEach(heading => observer.observe(heading));
}
// Запуск для обычной загрузки и для Astro ViewTransitions
document.addEventListener('DOMContentLoaded', setupTOC);
document.addEventListener('astro:page-load', setupTOC);
</script>

View file

@ -20,16 +20,6 @@ const {
imageUrl = '/images/blog/default.avif',
slug = '#'
} = Astro.props;
// Форматируем дату
const formatDate = (dateStr: string) => {
const d = new Date(dateStr);
return d.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
};
---
<article class="blog-card" data-animation="fade-up">
@ -53,7 +43,7 @@ const formatDate = (dateStr: string) => {
<line x1="8" x2="8" y1="2" y2="6"></line>
<line x1="3" x2="21" y1="10" y2="10"></line>
</svg>
{formatDate(date)}
{date}
</span>
<span class="meta-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon">
@ -178,7 +168,7 @@ const formatDate = (dateStr: string) => {
.card-meta {
display: flex;
align-items: center;
gap: 1.25rem;
justify-content: space-between;
padding-top: 1rem;
border-top: 1px solid #f1f5f9;
margin-top: auto;

View file

@ -37,8 +37,8 @@ const { initialLikes = 0, initialDislikes = 0, postId } = Astro.props;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 0.875rem;
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(15, 23, 42, 0.75);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 2rem;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);

View file

@ -76,7 +76,7 @@ const encodedUrl = encodeURIComponent(url);
}
.share-label {
color: #64748b;
color: #ffffff;
font-size: 0.875rem;
font-weight: 600;
}

View file

@ -62,6 +62,16 @@ const filteredPosts = currentSlug
border-top: 2px solid #f1f5f9;
}
/* Сбрасываем нумерацию заголовков от ArticleLayout */
.related-posts .blog-card .card-title {
counter-increment: none !important;
display: block !important;
}
.related-posts .blog-card .card-title::before {
display: none !important;
}
.section-title {
color: #1e293b;
font-size: clamp(1.5rem, 3vw, 2rem);

View file

@ -1,7 +1,7 @@
---
title: "Что делать при ДТП: пошаговая инструкция 2024"
description: "Подробный разбор действий после дорожно-транспортного происшествия. Как оформить ДТП, какие документы собрать и куда обращаться за компенсацией."
author: "Юрист АВ"
author: "Автоюрист"
category: "ДТП"
categoryColor: "bg-red"
date: 2024-03-20

View file

@ -48,297 +48,231 @@ const {
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicons/favicon.svg" />
<link rel="icon" href="/favicons/favicon.ico" />
<title>{title} {SITE_TITLE_SUFFIX}</title>
<meta name="description" content={description} />
{canonicalLink && <link rel="canonical" href={canonicalLink} />}
<meta name="yandex-verification" content="be3edfd138348e43" />
</head>
<body>
<Header />
<main class="main-content">
{breadcrumbs && breadcrumbs.length > 0 && (
<div class="breadcrumbs-wrapper">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicons/favicon.svg" />
<link rel="icon" href="/favicons/favicon.ico" />
<title>{title} {SITE_TITLE_SUFFIX}</title>
<meta name="description" content={description} />
{canonicalLink && <link rel="canonical" href={canonicalLink} />}
</head>
<body>
<Header />
<main class="main-content">
{breadcrumbs && breadcrumbs.length > 0 && (
<div class="breadcrumbs-wrapper">
<div class="site-container">
<Breadcrumbs items={breadcrumbs} />
</div>
)}
</div>
)}
<!-- Hero с полноширинным изображением -->
<section class="article-hero">
<div class="article-hero-image">
<img src={heroImage} alt={heroAlt} />
<div class="article-hero-overlay"></div>
</div>
<!-- HERO SECTION С ЭФФЕКТОМ ПРОЗРЕНИЯ -->
<section class="article-hero">
<div class="article-hero-image">
<img src={heroImage} alt={heroAlt} loading="eager" />
<div class="article-hero-overlay"></div>
</div>
<div class="article-hero-content">
<div class="site-container">
<div class="article-hero-inner">
<div class="article-hero-left">
<span class="article-category-badge">{category}</span>
<h1 class="article-title">{postTitle}</h1>
<div class="article-hero-content">
<div class="site-container h-full">
<div class="article-hero-inner">
<div class="article-hero-top">
<div class="category-strip">
<span class="category-text">{category}</span>
</div>
<h1 class="article-title">{postTitle}</h1>
</div>
<div class="article-meta">
<span class="meta-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon">
<rect width="18" height="18" x="3" y="4" rx="2" ry="2"></rect>
<line x1="16" x2="16" y1="2" y2="6"></line>
<line x1="8" x2="8" y1="2" y2="6"></line>
<line x1="3" x2="21" y1="10" y2="10"></line>
</svg>
{date}
</span>
<span class="meta-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon">
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<span class="meta-author">{author}</span>
</span>
<span class="meta-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
{readTime}
</span>
</div>
</div>
<div class="article-hero-bottom">
<div class="article-meta">
<span class="meta-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon"><rect width="18" height="18" x="3" y="4" rx="2" ry="2"></rect><line x1="16" x2="16" y1="2" y2="6"></line><line x1="8" x2="8" y1="2" y2="6"></line><line x1="3" x2="21" y1="10" y2="10"></line></svg>
{date}
</span>
<span class="meta-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
<span class="meta-author">{author}</span>
</span>
<span class="meta-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
{readTime}
</span>
</div>
<div class="article-hero-right">
<div class="article-actions">
<PostReactionButtons
postId={postId}
initialLikes={initialLikes}
initialDislikes={initialDislikes}
/>
<PostSocialShare
title={postTitle}
url={postUrl}
/>
</div>
<div class="article-actions">
<PostReactionButtons postId={postId} initialLikes={initialLikes} initialDislikes={initialDislikes} />
<div class="share-wrapper">
<PostSocialShare title={postTitle} url={postUrl} />
</div>
</div>
</div>
</div>
</section>
<!-- Основной контент -->
<div class="article-body">
<div class="site-container">
<div class="article-content-wrapper">
<slot />
</div>
</div>
</div>
</main>
<Footer />
<ConsultationModal />
</body>
</html>
</div>
</section>
<!-- BODY SECTION: СЕТКА С ЛИПКИМ САЙДБАРОМ -->
<div class="article-body">
<div class="site-container">
<div class="article-layout-grid">
<div class="article-main">
<article class="article-content-wrapper" id="post-content">
<slot />
</article>
</div>
<aside class="article-sidebar" id="sticky-sidebar">
<slot name="sidebar" />
</aside>
</div>
</div>
</div>
</main>
<Footer />
<script>
function setupArticleLogic() {
const sidebar = document.getElementById('sticky-sidebar');
const content = document.getElementById('post-content');
if (!sidebar || !content) return;
const tocLinks = sidebar.querySelectorAll<HTMLAnchorElement>('.toc-link');
const tocItems = sidebar.querySelectorAll('.toc-item');
const articleHeadings = content.querySelectorAll('h2, h3');
const HEADER_OFFSET = 120;
// 1. АВТОПРИВЯЗКА ID К ЗАГОЛОВКАМ (Решает проблему heading-0 не найден)
tocLinks.forEach((link, index) => {
const id = link.getAttribute('href')?.slice(1);
const heading = articleHeadings[index];
if (id && heading) {
heading.id = id;
}
});
// 2. ПЛАВНЫЙ СКРОЛЛ
tocLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const id = link.getAttribute('href')?.slice(1);
const target = id ? document.getElementById(id) : null;
if (target) {
const targetPos = target.getBoundingClientRect().top + window.pageYOffset - HEADER_OFFSET;
window.scrollTo({ top: targetPos, behavior: 'smooth' });
history.pushState(null, '', `#${id}`);
}
});
});
// 3. SCROLL SPY (ПОДСВЕТКА)
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const activeId = entry.target.id;
tocItems.forEach(item => {
const match = item.getAttribute('data-toc-target') === activeId;
item.classList.toggle('active', match);
});
}
});
}, {
root: null,
rootMargin: `-${HEADER_OFFSET}px 0px -70% 0px`,
threshold: 0
});
articleHeadings.forEach(h => observer.observe(h));
// 4. КНОПКА КОНСУЛЬТАЦИИ
const consultationBtn = document.getElementById("consultation-btn");
consultationBtn?.addEventListener("click", () => {
window.dispatchEvent(new CustomEvent("open-modal", { detail: "consultation-modal" }));
});
}
document.addEventListener("DOMContentLoaded", setupArticleLogic);
document.addEventListener("astro:page-load", setupArticleLogic);
</script>
<style is:global>
/* ЧЕРНАЯ НУМЕРАЦИЯ */
#post-content { counter-reset: post-h2; }
#post-content h2 { counter-reset: post-h3; counter-increment: post-h2; display: flex; align-items: baseline; gap: 0.6rem; color: #1e3050; font-weight: 800; }
#post-content h2::before { content: counter(post-h2) "."; color: #1e3050 !important; flex-shrink: 0; }
#post-content h3 { counter-increment: post-h3; display: flex; align-items: baseline; gap: 0.5rem; color: #1e3050; font-weight: 700; }
#post-content h3::before { content: counter(post-h2) "." counter(post-h3) "."; color: #1e3050 !important; font-size: 0.9em; flex-shrink: 0; }
/* Исключаем заголовок "Читайте также" из нумерации */
#post-content .related-posts .section-title {
counter-increment: none !important;
display: block !important;
}
#post-content .related-posts .section-title::before {
content: none !important;
}
/* СКРОЛЛ-ФИКС */
#post-content h2, #post-content h3 { scroll-margin-top: 125px; }
/* СТИЛЬ АКТИВНОГО ПУНКТА В TOC */
.toc-item.active .toc-link {
color: #eac26e !important;
font-weight: 700;
background: rgba(234, 194, 110, 0.1);
}
.toc-item.active .toc-link::before { color: #eac26e !important; }
html, body { overflow-x: clip !important; }
</style>
<style>
.main-content {
padding-top: 0;
}
.h-full { height: 100%; }
.main-content { padding-top: 0; overflow: visible; }
.breadcrumbs-wrapper { padding-top: 5.5rem; background: #f8fafc; }
.breadcrumbs-wrapper {
padding-top: 4.75rem;
background: #f8fafc;
border-bottom: 1px solid rgba(30, 48, 80, 0.05);
}
/* HERO SECTION */
.article-hero { position: relative; width: 100%; height: 550px; background: #0a1a2e; overflow: hidden; }
.article-hero-image { position: absolute; inset: 0; z-index: 1; }
.article-hero-image img { width: 100%; height: 100%; object-fit: cover; filter: brightness(0.35) grayscale(0.2); transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); transform: scale(1.04); }
.article-hero-overlay { position: absolute; inset: 0; background: linear-gradient(180deg, transparent 0%, rgba(10, 26, 46, 0.9) 100%); z-index: 2; transition: opacity 0.7s ease; }
/* Article Hero */
.article-hero {
position: relative;
width: 100%;
min-height: 500px;
display: flex;
align-items: flex-end;
}
.article-hero:hover .article-hero-image img { filter: brightness(1) grayscale(0); transform: scale(1); }
.article-hero:hover .article-hero-overlay { opacity: 0.4; }
.article-hero-image {
position: absolute;
inset: 0;
z-index: 0;
}
.article-hero-content { position: relative; z-index: 10; height: 100%; }
.article-hero-inner { display: flex; flex-direction: column; height: 100%; padding: 3rem 0; }
.article-hero-top { margin-bottom: 2rem; }
.article-hero-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.category-strip { background: #eac26e; padding: 0.5rem 1.5rem; display: inline-block; border-radius: 4px; margin-bottom: 2rem; }
.category-text { color: #0a1a2e; font-weight: 800; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 1px; }
.article-title { color: #fff; font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 900; line-height: 1.15; max-width: 950px; text-shadow: 0 4px 15px rgba(0,0,0,0.4); }
.article-hero-overlay {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(10, 37, 64, 0.3) 0%, rgba(10, 37, 64, 0.85) 100%);
}
.article-hero-bottom { margin-top: auto; display: flex; justify-content: space-between; align-items: center; padding-top: 2rem; border-top: 1px solid rgba(255, 255, 255, 0.15); }
.article-meta { display: flex; gap: 2.5rem; }
.meta-item { display: flex; align-items: center; gap: 0.6rem; color: #fff; font-size: 0.95rem; }
.meta-icon { width: 1.1rem; height: 1.1rem; color: #eac26e; }
.meta-author { color: #eac26e; font-weight: 700; }
.article-hero-content {
position: relative;
z-index: 2;
width: 100%;
padding: 3rem 0;
}
/* GRID & STICKY SIDEBAR */
.article-body { padding: 4rem 0; background: #f8fafc; overflow: visible; }
.article-layout-grid { display: flex; gap: 3.5rem; align-items: flex-start; }
.article-main { flex: 1; min-width: 0; }
.article-content-wrapper { background: #fff; padding: 1.5rem 2.5rem 2.5rem; border-radius: 2rem; box-shadow: 0 4px 30px rgba(0,0,0,0.02); }
.article-hero-inner {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 2rem;
flex-wrap: wrap;
}
.article-sidebar { width: 320px; flex-shrink: 0; position: sticky; top: 110px; z-index: 50; }
.article-hero-left {
flex: 1;
min-width: 0;
}
.article-category-badge {
display: inline-block;
padding: 0.35rem 0.75rem;
background: linear-gradient(135deg, #d4af37 0%, #eac26e 100%);
color: #1e293b;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
border-radius: 0.5rem;
margin-bottom: 1rem;
}
.article-title {
color: #ffffff;
font-size: clamp(1.75rem, 4vw, 3rem);
font-weight: 800;
margin: 0 0 1.5rem;
letter-spacing: -0.02em;
line-height: 1.2;
}
.article-meta {
display: flex;
gap: 1.25rem;
flex-wrap: wrap;
}
.meta-item {
display: flex;
align-items: center;
gap: 0.4rem;
color: rgba(255, 255, 255, 0.8);
font-size: 0.875rem;
font-weight: 500;
}
.meta-icon {
width: 1rem;
height: 1rem;
}
.meta-author {
color: #eac26e;
font-weight: 600;
}
.article-hero-right {
flex-shrink: 0;
}
.article-actions {
display: flex;
gap: 1.5rem;
align-items: center;
flex-wrap: wrap;
}
/* Article Body */
.article-body {
padding: 3rem 0;
background: #f8fafc;
}
.article-content-wrapper {
max-width: 1200px;
margin: 0 auto;
background: #ffffff;
border-radius: 1.5rem;
padding: 3rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.article-content-wrapper > :last-child {
margin-bottom: 0;
}
@media (max-width: 1024px) {
.article-hero-inner {
flex-direction: column;
align-items: flex-start;
}
.article-hero-right {
align-self: flex-start;
}
@media (max-width: 1200px) {
.article-sidebar { display: none; }
.article-layout-grid { flex-direction: column; }
}
@media (max-width: 768px) {
.article-hero {
min-height: 400px;
}
.article-hero-content {
padding: 2rem 0;
}
.article-title {
font-size: 1.75rem;
}
.article-meta {
flex-direction: column;
gap: 0.75rem;
}
.article-actions {
flex-direction: column;
align-items: flex-start;
}
.article-content-wrapper {
padding: 2rem;
}
.article-hero { height: auto; min-height: 550px; }
.article-title { font-size: 1.75rem; }
.article-hero-bottom { flex-direction: column; align-items: flex-start; gap: 2rem; }
.article-actions { width: 100%; justify-content: space-between; }
.article-content-wrapper { padding: 2.5rem 1.5rem; }
}
</style>
<script>
// Клиентский скрипт для открытия модального окна
document.addEventListener("DOMContentLoaded", () => {
const btn = document.getElementById("consultation-btn");
btn?.addEventListener("click", () => {
window.dispatchEvent(
new CustomEvent("open-modal", {
detail: "consultation-modal",
}),
);
});
});
// Для Astro View Transitions
document.addEventListener("astro:page-load", () => {
const btn = document.getElementById("consultation-btn");
btn?.addEventListener("click", () => {
window.dispatchEvent(
new CustomEvent("open-modal", {
detail: "consultation-modal",
}),
);
});
});
</script>
</style>

View file

@ -3,6 +3,7 @@ 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 ArticleTableOfContents from '@components/blog/ArticleTableOfContents.astro';
import { getCollection, getEntry, render } from 'astro:content';
export const prerender = false;
@ -28,6 +29,24 @@ if (!post) {
const { Content } = await render(post);
// Извлекаем заголовки из MDX тела для оглавления
const body = post.body || '';
const headingRegex = /^(#{2,3})\s+(.+)$/gm;
const tocItems: { id: string; text: string; level: number }[] = [];
let match;
let headingIndex = 0;
while ((match = headingRegex.exec(body)) !== null) {
const level = match[1].length;
const text = match[2].trim();
// Генерируем ID из текста заголовка
const id = text.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
tocItems.push({ level, id: `heading-${headingIndex++}`, text });
}
console.log('=== TOC ITEMS ===', tocItems);
// Форматируем дату
const formatDate = (date: Date) => {
return date.toLocaleDateString('ru-RU', {
@ -74,7 +93,7 @@ const allPosts = await getCollection('blog');
</div>
<!-- Форма комментариев -->
<PostCommentForm
<PostCommentForm
postId={post.id}
isAuthorized={isAuthorized}
/>
@ -84,16 +103,17 @@ const allPosts = await getCollection('blog');
posts={allPosts}
currentSlug={post.id}
/>
<!-- Оглавление в сайдбаре -->
<ArticleTableOfContents items={tocItems} slot="sidebar" />
</ArticleLayout>
<style>
/* Post Content */
.post-content {
padding: 3rem;
padding: 0;
color: #334155;
line-height: 1.8;
max-width: 1200px;
margin: 0 auto;
}
.post-content :global(h2) {
@ -135,26 +155,9 @@ const allPosts = await getCollection('blog');
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;
padding: 2rem 0;
}
.post-content :global(h2) {