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

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

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>