feat(i18n): implement comprehensive blog post enhancements
- Add avatar to personal info in data.ts - Remove redundant headings from blog posts - Reorder imports in utils.ts for consistency - Implement new blog layout components including: - PostMeta for displaying post metadata - TableOfContents for navigation - BlogNavigation for post pagination - ShareButtons for social sharing - AuthorCard for author information - Enhance BlogPostLayout with: - Improved typography and spacing - Responsive sidebar layout - Dark mode support - Better code block styling - Remove outdated i18n guide documentation - Add comprehensive styling for all new components
This commit is contained in:
141
src/components/layout/TableOfContents.astro
Normal file
141
src/components/layout/TableOfContents.astro
Normal file
@@ -0,0 +1,141 @@
|
||||
---
|
||||
import type { Lang } from '../../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
lang: Lang;
|
||||
}
|
||||
|
||||
const { lang } = Astro.props;
|
||||
const title = lang === 'zh' ? '目录' : 'Table of Contents';
|
||||
---
|
||||
|
||||
<div class="max-xl:hidden">
|
||||
<div id="nav-content" class="sticky w-72 top-14 max-h-[calc(100svh-3.5rem)] overflow-x-hidden">
|
||||
<div class="flex flex-col gap-3 p-4">
|
||||
<h3 class="dark:text-zinc-400 text-blacktext/90 font-bold tracking-wide text-sm sm:text-base uppercase flex items-center mb-4">
|
||||
<svg class="w-4 h-4 sm:w-5 sm:h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
{title}
|
||||
</h3>
|
||||
<div class="text-neutral-500 dark:text-neutral-300">
|
||||
<ul id="toc-list" class="leading-relaxed text-sm sm:text-base border-l dark:border-neutral-500/20 border-blacktext/20">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Initialize table of contents functionality
|
||||
* Extracts headings from article content and creates navigation links
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const tocList = document.getElementById("toc-list");
|
||||
const content = document.querySelector("article");
|
||||
|
||||
if (!tocList || !content) return;
|
||||
|
||||
// Extract h1, h2, h3, h4 headings from article content
|
||||
const headers = content.querySelectorAll("h1, h2, h3, h4");
|
||||
let currentUl = tocList;
|
||||
|
||||
/**
|
||||
* Process each heading and create corresponding TOC entry
|
||||
*/
|
||||
headers.forEach((header, index) => {
|
||||
// Generate ID if not present
|
||||
if (!header.id) {
|
||||
header.id = header.textContent?.trim().toLowerCase().replace(/\s+/g, "-") + "-" + index;
|
||||
}
|
||||
|
||||
const li = document.createElement("li");
|
||||
const link = document.createElement("a");
|
||||
link.href = `#${header.id}`;
|
||||
link.textContent = header.textContent?.trim() || header.id;
|
||||
|
||||
// Apply styling based on heading level
|
||||
const level = parseInt(header.tagName.charAt(1));
|
||||
link.classList.add(
|
||||
"block", "w-full", "text-left", "py-2", "px-3", "rounded-lg", "text-sm",
|
||||
"transition-all", "duration-200", "border-l", "border-transparent",
|
||||
"text-muted-foreground", "hover:text-foreground", "hover:bg-muted/50"
|
||||
);
|
||||
|
||||
// Add indentation based on heading level
|
||||
if (level === 1) {
|
||||
link.classList.add("font-semibold");
|
||||
} else if (level === 2) {
|
||||
link.classList.add("ml-3");
|
||||
} else if (level === 3) {
|
||||
link.classList.add("ml-6");
|
||||
} else if (level === 4) {
|
||||
link.classList.add("ml-9");
|
||||
}
|
||||
|
||||
li.appendChild(link);
|
||||
tocList.appendChild(li);
|
||||
|
||||
/**
|
||||
* Handle smooth scroll on link click
|
||||
*/
|
||||
link.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
const targetElement = document.getElementById(header.id);
|
||||
if (targetElement) {
|
||||
targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Set up intersection observer for active section tracking
|
||||
*/
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
const id = entry.target.getAttribute("id");
|
||||
const link = document.querySelector(`#toc-list a[href="#${id}"]`);
|
||||
|
||||
if (entry.isIntersecting) {
|
||||
// Remove active state from all links
|
||||
document.querySelectorAll("#toc-list a").forEach((el) => {
|
||||
el.classList.remove(
|
||||
"bg-purple-500/10", "text-purple-500", "border-l-2", "border-purple-500"
|
||||
);
|
||||
el.classList.add("text-muted-foreground");
|
||||
});
|
||||
|
||||
// Add active state to current link
|
||||
if (link) {
|
||||
link.classList.remove("text-muted-foreground");
|
||||
link.classList.add(
|
||||
"bg-purple-500/10", "text-purple-500", "border-l-2", "border-purple-500"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
rootMargin: "-20% 0% -35% 0%",
|
||||
threshold: 0
|
||||
}
|
||||
);
|
||||
|
||||
// Observe all headings for intersection
|
||||
headers.forEach((header) => observer.observe(header));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Additional styling for smooth transitions */
|
||||
#toc-list a:hover {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
#toc-list a:focus {
|
||||
outline: 2px solid rgba(168, 85, 247, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user