summaryrefslogtreecommitdiff
path: root/src/pages/posts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages/posts')
-rw-r--r--src/pages/posts/[slug].astro19
-rw-r--r--src/pages/posts/feed.xml.ts21
-rw-r--r--src/pages/posts/index.astro88
3 files changed, 128 insertions, 0 deletions
diff --git a/src/pages/posts/[slug].astro b/src/pages/posts/[slug].astro
new file mode 100644
index 0000000..0f29ec5
--- /dev/null
+++ b/src/pages/posts/[slug].astro
@@ -0,0 +1,19 @@
+---
+import { getCollection, type CollectionEntry } from 'astro:content';
+import PostLayout from '../../layouts/PostLayout.astro';
+
+export async function getStaticPaths() {
+ const posts = await getCollection('posts');
+ return posts.map((post: CollectionEntry<'posts'>) => ({
+ params: { slug: post.slug },
+ props: { post },
+ }));
+}
+
+const { post } = Astro.props as { post: CollectionEntry<'posts'> };
+const { Content } = await post.render();
+---
+
+<PostLayout title={post.data.title}>
+ <Content />
+</PostLayout>
diff --git a/src/pages/posts/feed.xml.ts b/src/pages/posts/feed.xml.ts
new file mode 100644
index 0000000..43b9b84
--- /dev/null
+++ b/src/pages/posts/feed.xml.ts
@@ -0,0 +1,21 @@
+import rss from '@astrojs/rss';
+import { getCollection } from 'astro:content';
+import { marked } from 'marked';
+import type { APIContext } from 'astro';
+
+export async function GET(context: APIContext) {
+ const posts = await getCollection('posts');
+ posts.sort((a, b) => b.data.date.localeCompare(a.data.date));
+
+ return rss({
+ title: "Matthew Kosarek's Blog",
+ description: 'Updates and thoughts from Matthew Kosarek',
+ site: context.site ?? 'https://matthewkosarek.xyz',
+ items: posts.map(post => ({
+ title: post.data.title,
+ pubDate: new Date(post.data.date),
+ link: `/posts/${post.slug}/`,
+ content: marked(post.body) as string,
+ })),
+ });
+}
diff --git a/src/pages/posts/index.astro b/src/pages/posts/index.astro
new file mode 100644
index 0000000..b3ea740
--- /dev/null
+++ b/src/pages/posts/index.astro
@@ -0,0 +1,88 @@
+---
+import { getCollection, type CollectionEntry } from 'astro:content';
+import BaseLayout from '../../layouts/BaseLayout.astro';
+import '../../styles/post.css';
+import '../../styles/sitemap.css';
+
+const allPosts = await getCollection('posts');
+allPosts.sort((a: CollectionEntry<'posts'>, b: CollectionEntry<'posts'>) => b.data.date.localeCompare(a.data.date));
+
+function formatDate(dateStr: string): string {
+ const [year, month, day] = dateStr.split('-').map(Number);
+ const d = new Date(year, month - 1, day);
+ return d.toLocaleDateString('en-US', { month: 'long', day: '2-digit', year: 'numeric' });
+}
+---
+
+<BaseLayout title="Posts">
+ <div class="org-article-title">
+ <h1></h1>
+ <a href="/posts/feed.xml">RSS Feed</a>
+ </div>
+
+ <div id="tag-filter-container"></div>
+
+ <div id="content" class="content">
+ <ul class="org-ul" id="posts-list">
+ {allPosts.map((post: CollectionEntry<'posts'>) => (
+ <li data-tags={post.data.tags.join(',')}>
+ <p><a href={`/posts/${post.slug}`}>{post.data.title}</a></p>
+ <div class="sitemap_date"><p>{formatDate(post.data.date)}</p></div>
+ <div class="sitemap_tag"><p>{post.data.tags.join(',')}</p></div>
+ </li>
+ ))}
+ </ul>
+ </div>
+
+ <script is:inline>
+ (function () {
+ const list = document.getElementById('posts-list');
+ const filterContainer = document.getElementById('tag-filter-container');
+ if (!list || !filterContainer) return;
+
+ const items = Array.from(list.querySelectorAll('li[data-tags]'));
+
+ // Collect unique tags
+ const tagSet = new Set();
+ items.forEach(item => {
+ const tags = item.getAttribute('data-tags') || '';
+ tags.split(',').filter(Boolean).forEach(t => tagSet.add(t.trim()));
+ });
+
+ if (tagSet.size === 0) return;
+
+ // Track which tags are enabled
+ const enabledTags = new Set(tagSet);
+
+ function applyFilter() {
+ items.forEach(item => {
+ const itemTags = (item.getAttribute('data-tags') || '')
+ .split(',')
+ .filter(Boolean)
+ .map(t => t.trim());
+ const visible = itemTags.some(t => enabledTags.has(t)) || itemTags.length === 0;
+ item.style.display = visible ? '' : 'none';
+ });
+ }
+
+ // Create filter buttons
+ tagSet.forEach(tag => {
+ const btn = document.createElement('span');
+ btn.textContent = tag;
+ btn.className = 'tag-filter-item';
+ btn.dataset.tag = tag;
+ btn.addEventListener('click', () => {
+ if (enabledTags.has(tag)) {
+ enabledTags.delete(tag);
+ btn.classList.add('disabled');
+ } else {
+ enabledTags.add(tag);
+ btn.classList.remove('disabled');
+ }
+ applyFilter();
+ });
+ filterContainer.appendChild(btn);
+ });
+ })();
+ </script>
+</BaseLayout>