Update post index, rss feed, 404 page

This commit is contained in:
2025-01-28 10:17:01 -08:00
parent bc4ddb7eae
commit 6ef97bb5f7
8 changed files with 124 additions and 12 deletions

View File

@@ -22,6 +22,7 @@
"@astrojs/sitemap": "^3.2.1", "@astrojs/sitemap": "^3.2.1",
"@react-hook/intersection-observer": "^3.1.2", "@react-hook/intersection-observer": "^3.1.2",
"lucide-react": "^0.468.0", "lucide-react": "^0.468.0",
"marked": "^15.0.6",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-responsive": "^10.0.0", "react-responsive": "^10.0.0",

10
src/pnpm-lock.yaml generated
View File

@@ -23,6 +23,9 @@ importers:
lucide-react: lucide-react:
specifier: ^0.468.0 specifier: ^0.468.0
version: 0.468.0(react@18.3.1) version: 0.468.0(react@18.3.1)
marked:
specifier: ^15.0.6
version: 15.0.6
react: react:
specifier: ^18.3.1 specifier: ^18.3.1
version: 18.3.1 version: 18.3.1
@@ -1353,6 +1356,11 @@ packages:
markdown-table@3.0.4: markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
marked@15.0.6:
resolution: {integrity: sha512-Y07CUOE+HQXbVDCGl3LXggqJDbXDP2pArc2C1N1RRMN0ONiShoSsIInMd5Gsxupe7fKLpgimTV+HOJ9r7bA+pg==}
engines: {node: '>= 18'}
hasBin: true
matchmediaquery@0.4.2: matchmediaquery@0.4.2:
resolution: {integrity: sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==} resolution: {integrity: sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==}
@@ -3717,6 +3725,8 @@ snapshots:
markdown-table@3.0.4: {} markdown-table@3.0.4: {}
marked@15.0.6: {}
matchmediaquery@0.4.2: matchmediaquery@0.4.2:
dependencies: dependencies:
css-mediaquery: 0.1.2 css-mediaquery: 0.1.2

View File

@@ -0,0 +1,56 @@
import React, { useState, useEffect } from "react";
const GlitchText = () => {
const originalText = 'Error 404';
const [characters, setCharacters] = useState(
originalText.split("").map(char => ({ char, isGlitched: false }))
);
const glitchChars = "!<>-_\\/[]{}—=+*^?#________";
useEffect(() => {
const glitchInterval = setInterval(() => {
if (Math.random() < 0.2) { // 20% chance to trigger glitch
setCharacters(prev => {
return originalText.split('').map((originalChar, index) => {
if (Math.random() < 0.3) { // 30% chance to glitch each character
return {
char: glitchChars[Math.floor(Math.random() * glitchChars.length)],
isGlitched: true
};
}
return { char: originalChar, isGlitched: false };
});
});
// Reset after short delay
setTimeout(() => {
setCharacters(originalText.split('').map(char => ({
char,
isGlitched: false
})));
}, 100);
}
}, 50);
return () => clearInterval(glitchInterval);
}, []);
return (
<div className="relative">
<h1 className="text-6xl font-bold mb-4 relative">
<span className="relative inline-block">
{characters.map((charObj, index) => (
<span
key={index}
className={charObj.isGlitched ? "text-red" : "text-purple"}
>
{charObj.char}
</span>
))}
</span>
</h1>
</div>
);
};
export default GlitchText;

View File

@@ -0,0 +1,38 @@
import React from "react";
import { RssIcon, TagIcon, TrendingUpIcon } from "lucide-react";
export const BlogHeader = () => {
return (
<div className="w-full max-w-6xl mx-auto px-4 pt-24 sm:pt-24">
<h1 className="text-2xl sm:text-3xl font-bold text-purple mb-3 text-center px-4 leading-relaxed">
Latest Thoughts <br className="sm:hidden" />
& Writings
</h1>
<div className="flex flex-wrap justify-center gap-4 mb-12 text-sm sm:text-base">
<a
href="/rss"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-background border border-foreground/20 text-orange hover:text-orange-bright hover:border-orange/50 transition-colors duration-200"
>
<RssIcon className="w-4 h-4" />
<span>RSS Feed</span>
</a>
<a
href="/blog/tags"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-background border border-foreground/20 text-aqua hover:text-aqua-bright hover:border-aqua/50 transition-colors duration-200"
>
<TagIcon className="w-4 h-4" />
<span>Browse Tags</span>
</a>
<a
href="/blog/popular"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-background border border-foreground/20 text-blue hover:text-blue-bright hover:border-blue/50 transition-colors duration-200"
>
<TrendingUpIcon className="w-4 h-4" />
<span>Most Popular</span>
</a>
</div>
</div>
);
};

View File

@@ -27,12 +27,7 @@ const formatDate = (dateString: string) => {
export const BlogPostList = ({ posts }: BlogPostListProps) => { export const BlogPostList = ({ posts }: BlogPostListProps) => {
return ( return (
<div className="w-full max-w-6xl mx-auto pt-24 sm:pt-24"> <div className="w-full max-w-6xl mx-auto">
<h1 className="text-2xl sm:text-3xl font-bold text-purple mb-12 text-center px-4 leading-relaxed">
Latest Thoughts <br className="sm:hidden" />
& Writings
</h1>
<ul className="space-y-6 md:space-y-10"> <ul className="space-y-6 md:space-y-10">
{posts.map((post) => ( {posts.map((post) => (
<li key={post.slug} className="group px-4 md:px-0"> <li key={post.slug} className="group px-4 md:px-0">

View File

@@ -1,16 +1,16 @@
--- ---
import IndexLayout from "@/layouts/index.astro"; import IndexLayout from "@/layouts/index.astro";
import GlitchText from "@/components/404/glitched-text";
const title = "404 Not Found"; const title = "404 Not Found";
--- ---
<IndexLayout content={{ title: "404 | Timothy Pidashev" }}> <IndexLayout content={{ title: "404 | Timothy Pidashev" }}>
<main class="min-h-screen flex flex-col items-center justify-center p-4 text-center"> <main class="min-h-screen flex flex-col items-center justify-center p-4 text-center">
<h1 class="text-6xl font-bold mb-4">404</h1> <GlitchText client:only />
<p class="text-xl mb-8">Whoops! This page doesn't exist.</p> <p class="text-xl text-orange mb-8">Whoops! This page doesn't exist :(</p>
<button <button
onclick="window.history.back()" onclick="window.history.back()"
class="underline hover:opacity-70 transition-opacity" class="underline text-green hover:opacity-70 transition-opacity"
> >
go back go back
</button> </button>

View File

@@ -1,7 +1,9 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import ContentLayout from "@/layouts/content.astro"; import ContentLayout from "@/layouts/content.astro";
import { BlogHeader } from "@/components/blog/header";
import { BlogPostList } from "@/components/blog/post-list"; import { BlogPostList } from "@/components/blog/post-list";
const posts = (await getCollection("blog", ({ data }) => { const posts = (await getCollection("blog", ({ data }) => {
return data.isDraft !== true; return data.isDraft !== true;
})).sort((a, b) => { })).sort((a, b) => {
@@ -22,5 +24,6 @@ const posts = (await getCollection("blog", ({ data }) => {
title="Blog | Timothy Pidashev" title="Blog | Timothy Pidashev"
description="My experiences and technical insights into software development and the ever-evolving world of programming." description="My experiences and technical insights into software development and the ever-evolving world of programming."
> >
<BlogHeader client:load />
<BlogPostList posts={posts} client:load /> <BlogPostList posts={posts} client:load />
</ContentLayout> </ContentLayout>

View File

@@ -5,16 +5,25 @@ import type { APIContext } from "astro";
export async function GET(context: APIContext) { export async function GET(context: APIContext) {
const blog = await getCollection("blog"); const blog = await getCollection("blog");
const sortedPosts = blog
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
return rss({ return rss({
title: "Timothy Pidashev", title: "Timothy Pidashev",
description: "My experiences and technical insights into software development and the ever-evolving world of programming.", description: "My experiences and technical insights into software development and the ever-evolving world of programming.",
site: context.site!, site: context.site!,
items: blog.map((post) => ({ items: sortedPosts.map((post) => ({
title: post.data.title, title: post.data.title,
pubDate: post.data.date, pubDate: post.data.date,
description: post.data.description, description: post.data.description,
link: `/blog/${post.slug}/`, link: `/blog/${post.slug}/`,
author: post.data.author,
categories: post.data.tags,
enclosure: post.data.image ? {
url: new URL(`blog/${post.slug}/thumbnail.png`, context.site).toString(),
type: 'image/jpeg',
length: 0
} : undefined
})), })),
customData: `<language>en-us</language>`,
}); });
} }