mirror of
https://github.com/kucherenko/jscpd.git
synced 2026-09-19 08:11:03 +08:00
66a54c3ea7
- Register 'astro' format with .astro extension in src/formats.ts - Add tokenizeAstro() in src/languages/astro.ts: extracts and tokenizes frontmatter (typescript), <script> blocks (js/ts by lang attr), <style> blocks (css/scss/less by lang attr), and template (markup) independently - Dispatch to tokenizeAstro() in createTokenMapBasedOnCode() for format 'astro' - Add 31 tests in __tests__/astro.test.ts covering all extraction scenarios, line offset correctness, CRLF normalization, ReDoS guard, empty frontmatter - Add 6 test fixture files under __tests__/fixtures/astro/ - Update formats.test.ts and tokenize.test.ts with astro coverage entries - Add end-to-end integration fixtures in fixtures/astro/ (component1/2.astro)
163 lines
3.2 KiB
Plaintext
163 lines
3.2 KiB
Plaintext
---
|
|
import { getCollection } from 'astro:content';
|
|
import type { CollectionEntry } from 'astro:content';
|
|
|
|
export interface Props {
|
|
title: string;
|
|
description: string;
|
|
pubDate: Date;
|
|
tags: string[];
|
|
}
|
|
|
|
const { title, description, pubDate, tags } = Astro.props;
|
|
|
|
const allPosts = await getCollection('blog');
|
|
const relatedPosts = allPosts
|
|
.filter((post: CollectionEntry<'blog'>) => post.data.tags.some((tag: string) => tags.includes(tag)))
|
|
.filter((post: CollectionEntry<'blog'>) => post.data.title !== title)
|
|
.slice(0, 3);
|
|
|
|
const formattedDate = pubDate.toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
});
|
|
---
|
|
|
|
<article class="post">
|
|
<header class="post-header">
|
|
<h1 class="post-title">{title}</h1>
|
|
<p class="post-description">{description}</p>
|
|
<time class="post-date" datetime={pubDate.toISOString()}>{formattedDate}</time>
|
|
<ul class="post-tags">
|
|
{tags.map((tag) => (
|
|
<li class="post-tag"><a href={`/tags/${tag}`}>{tag}</a></li>
|
|
))}
|
|
</ul>
|
|
</header>
|
|
|
|
<div class="post-content">
|
|
<slot />
|
|
</div>
|
|
|
|
{relatedPosts.length > 0 && (
|
|
<aside class="related-posts">
|
|
<h2>Related Posts</h2>
|
|
<ul>
|
|
{relatedPosts.map((post) => (
|
|
<li>
|
|
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</aside>
|
|
)}
|
|
</article>
|
|
|
|
<script>
|
|
document.querySelectorAll('.post-tag a').forEach((link) => {
|
|
link.addEventListener('click', (event) => {
|
|
const tag = link.textContent;
|
|
console.log('Tag clicked:', tag);
|
|
});
|
|
});
|
|
|
|
const postContent = document.querySelector('.post-content');
|
|
if (postContent) {
|
|
const headings = postContent.querySelectorAll('h2, h3, h4');
|
|
headings.forEach((heading) => {
|
|
const id = heading.textContent?.toLowerCase().replace(/\s+/g, '-') ?? '';
|
|
heading.id = id;
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.post {
|
|
max-width: 768px;
|
|
margin: 0 auto;
|
|
padding: 2rem 1rem;
|
|
}
|
|
|
|
.post-header {
|
|
margin-bottom: 2rem;
|
|
border-bottom: 1px solid #e5e7eb;
|
|
padding-bottom: 1rem;
|
|
}
|
|
|
|
.post-title {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: #111827;
|
|
margin: 0 0 0.5rem;
|
|
}
|
|
|
|
.post-description {
|
|
font-size: 1.125rem;
|
|
color: #6b7280;
|
|
margin: 0 0 1rem;
|
|
}
|
|
|
|
.post-date {
|
|
font-size: 0.875rem;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.post-tags {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 1rem 0 0;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.post-tag a {
|
|
background: #f3f4f6;
|
|
color: #374151;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 9999px;
|
|
font-size: 0.875rem;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.post-tag a:hover {
|
|
background: #e5e7eb;
|
|
}
|
|
|
|
.post-content {
|
|
line-height: 1.75;
|
|
color: #374151;
|
|
}
|
|
|
|
.related-posts {
|
|
margin-top: 3rem;
|
|
padding-top: 1.5rem;
|
|
border-top: 1px solid #e5e7eb;
|
|
}
|
|
|
|
.related-posts h2 {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.related-posts ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
}
|
|
|
|
.related-posts li {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.related-posts a {
|
|
color: #2563eb;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.related-posts a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
</style>
|