mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 02:53:51 +00:00
Update post index, rss feed, 404 page
This commit is contained in:
@@ -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
10
src/pnpm-lock.yaml
generated
@@ -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
|
||||||
|
|||||||
56
src/src/components/404/glitched-text.tsx
Normal file
56
src/src/components/404/glitched-text.tsx
Normal 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;
|
||||||
38
src/src/components/blog/header.tsx
Normal file
38
src/src/components/blog/header.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>`,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user