chore: remove unused images, update project image paths to webp format, and enhance accessibility with aria-labels across components

This commit is contained in:
cojocaru-david
2025-08-14 03:21:50 +03:00
parent 57dc793005
commit 865b182062
37 changed files with 152 additions and 88 deletions

View File

@@ -7,7 +7,6 @@ import icon from 'astro-icon'
import expressiveCode from 'astro-expressive-code' import expressiveCode from 'astro-expressive-code'
import { rehypeHeadingIds } from '@astrojs/markdown-remark' import { rehypeHeadingIds } from '@astrojs/markdown-remark'
import rehypeExternalLinks from 'rehype-external-links' import rehypeExternalLinks from 'rehype-external-links'
import rehypeKatex from 'rehype-katex'
import rehypePrettyCode from 'rehype-pretty-code' import rehypePrettyCode from 'rehype-pretty-code'
import remarkEmoji from 'remark-emoji' import remarkEmoji from 'remark-emoji'
import remarkMath from 'remark-math' import remarkMath from 'remark-math'
@@ -98,22 +97,16 @@ export default defineConfig({
markdown: { markdown: {
syntaxHighlight: false, syntaxHighlight: false,
rehypePlugins: [ rehypePlugins: [
[ rehypeDocument,
rehypeDocument,
{
css: 'https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css',
},
],
[ [
rehypeExternalLinks, rehypeExternalLinks,
{ {
target: '_blank', target: '_blank',
rel: ['nofollow', 'noreferrer', 'noopener'], ariaLabel: 'External link'
}, },
], ],
rehypeDemoteH1AndStripTitle, rehypeDemoteH1AndStripTitle,
rehypeHeadingIds, rehypeHeadingIds,
rehypeKatex,
[ [
rehypePrettyCode, rehypePrettyCode,
{ {

View File

@@ -70,7 +70,6 @@
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"rehype-document": "^7.0.3", "rehype-document": "^7.0.3",
"rehype-external-links": "^3.0.0", "rehype-external-links": "^3.0.0",
"rehype-katex": "^7.0.1",
"rehype-pretty-code": "^0.14.1", "rehype-pretty-code": "^0.14.1",
"remark-emoji": "^5.0.1", "remark-emoji": "^5.0.1",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 MiB

BIN
public/static/tailci.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

View File

@@ -7,7 +7,7 @@ function BuyMeCoffee({ classname }: { classname?: string }) {
href='https://ko-fi.com/cojoo' href='https://ko-fi.com/cojoo'
target='_blank' target='_blank'
className={cn( className={cn(
'border relative group w-36 mx-auto cursor-pointer h-32 grid place-content-center p-10 py-14 bg-primary rounded-md overflow-hidden', 'border relative group w-36 mx-auto cursor-pointer h-32 grid place-content-center p-10 py-14 bg-primary rounded-md overflow-hidden',
classname classname
)} )}
> >

View File

@@ -84,7 +84,7 @@ const Navbar = () => {
<> <>
<motion.header <motion.header
aria-label="Navigation" aria-label="Navigation"
role="navigation" role="banner"
layout={!isMobile} layout={!isMobile}
initial={sizeVariants[0]} initial={sizeVariants[0]}
animate={isMobile ? sizeVariants[0] : sizeVariants[scrollLevel]} animate={isMobile ? sizeVariants[0] : sizeVariants[scrollLevel]}
@@ -113,7 +113,6 @@ const Navbar = () => {
className="font-custom flex shrink-0 items-center gap-2 text-xl font-bold" className="font-custom flex shrink-0 items-center gap-2 text-xl font-bold"
aria-label="Home" aria-label="Home"
title="Home" title="Home"
navigation="true"
> >
<Logo className="h-8 w-8" /> <Logo className="h-8 w-8" />
<span className={ <span className={
@@ -123,7 +122,7 @@ const Navbar = () => {
</Link> </Link>
<div className="flex items-center gap-2 md:gap-4"> <div className="flex items-center gap-2 md:gap-4">
<nav className="hidden items-center gap-6 md:flex" aria-label="Main navigation" role="navigation"> <nav className="hidden items-center gap-6 md:flex" aria-label="Main navigation">
{NAV_LINKS.map((item) => { {NAV_LINKS.map((item) => {
const isActive = activePath.startsWith(item.href) && item.href !== "/"; const isActive = activePath.startsWith(item.href) && item.href !== "/";
return ( return (

View File

@@ -11,9 +11,8 @@ function Logo({ className }: { className?: string }) {
)} )}
role="img" role="img"
aria-label="Logo" aria-label="Logo"
aria-hidden="true" aria-hidden="true"
name="logo" focusable="false"
focusable="false"
> >
<path <path
d="M0 0h216.667C290.305 0 350 59.546 350 133s-59.695 133-133.333 133L0 0zm231.629 231.641c48.131-7.203 85.038-48.623 85.038-98.641 0-55.09-44.772-99.75-100-99.75h-54.862C185.557 59.722 200 94.678 200 133c0 17.331-2.96 33.991-8.405 49.492l40.034 49.149zm-66.243-81.326c.844-5.646 1.281-11.428 1.281-17.315 0-42.333-22.66-79.386-56.541-99.75H70.032l95.354 117.065z" d="M0 0h216.667C290.305 0 350 59.546 350 133s-59.695 133-133.333 133L0 0zm231.629 231.641c48.131-7.203 85.038-48.623 85.038-98.641 0-55.09-44.772-99.75-100-99.75h-54.862C185.557 59.722 200 94.678 200 133c0 17.331-2.96 33.991-8.405 49.492l40.034 49.149zm-66.243-81.326c.844-5.646 1.281-11.428 1.281-17.315 0-42.333-22.66-79.386-56.541-99.75H70.032l95.354 117.065z"

View File

@@ -8,6 +8,7 @@ export const SITE: Site = {
author: 'Cojocaru David', author: 'Cojocaru David',
locale: 'en-US', locale: 'en-US',
location: 'Romania', location: 'Romania',
email: 'contact@cojocarudavid.me'
} }
export const NAV_LINKS: SocialLink[] = [ export const NAV_LINKS: SocialLink[] = [

View File

@@ -97,7 +97,7 @@ export default function ThemeToggle() {
localStorage.setItem("theme", newTheme); localStorage.setItem("theme", newTheme);
}; };
return <button onClick={toggleTheme}>{theme === "light" ? "🌙" : "🌞"}</button>; return <button onClick={toggleTheme} aria-label="Toggle theme">{theme === "light" ? "🌙" : "🌞"}</button>;
} }
``` ```

View File

@@ -123,8 +123,8 @@ Create `index.html` in the same folder. Paste and save.
<body> <body>
<h1>💬 Mini Chat</h1> <h1>💬 Mini Chat</h1>
<div id="chat"></div> <div id="chat"></div>
<input id="msgBox" placeholder="Type and hit Enter"> <input id="msgBox" placeholder="Type and hit Enter" aria-label="Message input">
<button onclick="sendMsg()">Send</button> <button onclick="sendMsg()" aria-label="Send message">Send</button>
<script> <script>
const socket = new WebSocket('ws://localhost:8080'); const socket = new WebSocket('ws://localhost:8080');

View File

@@ -109,7 +109,7 @@ const Task = ({ task, onDelete, onToggle }) => {
/> />
<span>{task.text}</span> <span>{task.text}</span>
</div> </div>
<button onClick={() => onDelete(task.id)} className="delete-btn"> <button onClick={() => onDelete(task.id)} className="delete-btn" aria-label="Delete task">
Delete Delete
</button> </button>
</div> </div>
@@ -187,7 +187,7 @@ const TaskForm = ({ onAdd }) => {
placeholder="What needs to be done?" placeholder="What needs to be done?"
className="task-input" className="task-input"
/> />
<button type="submit" className="add-btn"> <button type="submit" className="add-btn" aria-label="Add task">
Add Task Add Task
</button> </button>
</form> </form>

View File

@@ -127,9 +127,9 @@ Open `index.html` and paste this:
<h1>🌤️ Weather Check</h1> <h1>🌤️ Weather Check</h1>
<div class="search-box"> <div class="search-box">
<input type="text" id="city-input" placeholder="Enter city name..."> <input type="text" id="city-input" placeholder="Enter city name..." aria-label="City input">
<button id="search-btn">Search</button> <button id="search-btn" aria-label="Search">Search</button>
<button id="location-btn">Use My Location</button> <button id="location-btn" aria-label="Use my location">Use My Location</button>
</div> </div>
<div id="weather-info" class="hidden"> <div id="weather-info" class="hidden">

View File

@@ -51,7 +51,7 @@ Instead of writing CSS like:
I just typed: I just typed:
```html ```html
<button class="px-8 py-3 bg-blue-500 text-white rounded-lg font-semibold hover:bg-blue-600 transition"> <button class="px-8 py-3 bg-blue-500 text-white rounded-lg font-semibold hover:bg-blue-600 transition" aria-label="Get started">
Get Started Get Started
</button> </button>
``` ```
@@ -420,7 +420,7 @@ Let's build something useful. A **responsive card** that looks great everywhere.
<div class="p-6"> <div class="p-6">
<h3 class="text-xl font-semibold text-gray-900">Card Title</h3> <h3 class="text-xl font-semibold text-gray-900">Card Title</h3>
<p class="mt-2 text-gray-600">Brief description here</p> <p class="mt-2 text-gray-600">Brief description here</p>
<button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"> <button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" aria-label="Learn more">
Learn More Learn More
</button> </button>
</div> </div>
@@ -432,7 +432,7 @@ Let's build something useful. A **responsive card** that looks great everywhere.
```html ```html
<!-- Changes to horizontal layout on desktop --> <!-- Changes to horizontal layout on desktop -->
<div class="max-w-4xl mx-auto md:flex"> <div class="max-w-4xl mx-auto md:flex">
<img class="md:w-48 md:h-auto" src="/img.jpg"> <img class="md:w-48 md:h-auto" src="/img.jpg" alt="Card image">
<div class="p-6"> <div class="p-6">
<!-- Same content, now horizontal --> <!-- Same content, now horizontal -->
</div> </div>

View File

@@ -103,12 +103,12 @@ Without dimensions, the browser can't reserve space. When the image finally load
Good: Good:
```html ```html
<img src="dog.jpg" width="800" height="450" loading="lazy"> <img src="dog.jpg" width="800" height="450" loading="lazy" alt="Dog">
``` ```
Bad: Bad:
```html ```html
<img src="dog.jpg" loading="lazy"> <img src="dog.jpg" loading="lazy" alt="Dog">
``` ```
### **Mistake 3: Overdoing Fade-In Animations** ### **Mistake 3: Overdoing Fade-In Animations**

View File

@@ -2,7 +2,7 @@
name: 'cojocarudavid.me (old)' name: 'cojocarudavid.me (old)'
description: 'Blazing fast personal website built with Astro.js and styled with Tailwind CSS.' description: 'Blazing fast personal website built with Astro.js and styled with Tailwind CSS.'
tags: ['astro', 'tailwindcss', 'typescript'] tags: ['astro', 'tailwindcss', 'typescript']
image: '../../../public/static/cojocarudavidme.png' image: '../../../public/static/cojocarudavidme.webp'
link: 'https://github.com/cojocaru-david/cojocarudavid.me' link: 'https://github.com/cojocaru-david/cojocarudavid.me'
startDate: '2023-10-16' startDate: '2023-10-16'
endDate: '2025-04-28' endDate: '2025-04-28'

View File

@@ -2,7 +2,7 @@
name: 'David Dark Code' name: 'David Dark Code'
description: 'David Dark is a dark theme for VS Code. It is based on the github theme and the default dark theme. It is designed to be easy on the eyes and to be used for long periods of time.' description: 'David Dark is a dark theme for VS Code. It is based on the github theme and the default dark theme. It is designed to be easy on the eyes and to be used for long periods of time.'
tags: ['vscode', 'theme', 'dark'] tags: ['vscode', 'theme', 'dark']
image: '../../../public/static/david-dark-code.png' image: '../../../public/static/david-dark-code.webp'
link: 'https://github.com/cojocaru-david/david-dark-code' link: 'https://github.com/cojocaru-david/david-dark-code'
startDate: '2023-10-17' startDate: '2023-10-17'
endDate: '2023-10-17' endDate: '2023-10-17'

View File

@@ -2,7 +2,7 @@
name: 'Dream Home Template' name: 'Dream Home Template'
description: 'A modern, responsive real estate platform built with React, Vite, and Tailwind CSS. Find your dream home with intuitive search, detailed property listings, and seamless user experience.' description: 'A modern, responsive real estate platform built with React, Vite, and Tailwind CSS. Find your dream home with intuitive search, detailed property listings, and seamless user experience.'
tags: ['react', 'vite', 'tailwindcss'] tags: ['react', 'vite', 'tailwindcss']
image: '../../../public/static/dream-home-template.png' image: '../../../public/static/dream-home-template.webp'
link: 'https://dreamhome.cojocarudavid.me' link: 'https://dreamhome.cojocarudavid.me'
startDate: '2025-04-16' startDate: '2025-04-16'
endDate: '2025-05-29' endDate: '2025-05-29'

View File

@@ -2,7 +2,7 @@
name: 'Modern Portfolio' name: 'Modern Portfolio'
description: 'Modern Portfolio is a personal website that showcases my work and projects. It is built with Astro.js and styled with Tailwind CSS, providing a fast and responsive user experience. The website features a clean design, easy navigation, and a focus on showcasing my skills and projects.' description: 'Modern Portfolio is a personal website that showcases my work and projects. It is built with Astro.js and styled with Tailwind CSS, providing a fast and responsive user experience. The website features a clean design, easy navigation, and a focus on showcasing my skills and projects.'
tags: ['astro', 'tailwindcss', 'typescript'] tags: ['astro', 'tailwindcss', 'typescript']
image: '../../../public/static/modern-portfolio.png' image: '../../../public/static/modern-portfolio.webp'
link: 'https://cojocarudavid.me' link: 'https://cojocarudavid.me'
startDate: '2025-03-30' startDate: '2025-03-30'
--- ---

View File

@@ -0,0 +1,48 @@
---
name: 'SnippetsLibrary'
description: 'A secure, lightning-fast code snippet manager to store, organize, and share your code with beautiful syntax highlighting.'
tags: ['react', 'vite', 'tailwindcss', 'typescript', 'bun', 'hono', 'drizzle-orm', 'postgresql', 'jwt', 'github-oauth', 'cloudflare-workers']
image: '../../../public/static/snippetslibrary.webp'
link: 'https://snippetslibrary.com'
startDate: '2025-07-23'
---
# SnippetsLibrary
Manage your personal and team code snippets with speed and confidence. SnippetsLibrary makes it easy to create, organize, search, and share snippets with first-class DX and beautiful syntax highlighting.
I have 50+ users and I'm working on a new version with a lot of new features.
And 200+ snippets.
## ✨ Highlights
- **Secure auth**: GitHub OAuth + JWT sessions
- **Sharing**: Public share links with SEO-friendly pages
- **Search & filter**: Language, visibility, keywords, pagination
- **Syntax highlighting**: 20+ languages, themes, copy-preserving formatting
- **Fast**: Bun runtime, Hono server, optimized client with Vite
## 🛠️ Tech Stack
- Frontend: React, Vite, Tailwind CSS, shadcn/ui
- Backend: Bun, Hono, JWT, Drizzle ORM, PostgreSQL
- Infra: Cloudflare Workers, GitHub Actions, Drizzle Kit
## 🚀 Quick Start
1. Clone the repo:
```bash
git clone https://github.com/cojocaru-david/snippetslibrary.com
cd snippetslibrary.com
```
2. Install deps: `bun install`
3. Configure `.env` in `server/` (DB, GitHub OAuth, JWT, FRONTEND_URL)
4. Migrate DB: `cd server && bun run db:migrate`
5. Start dev: `bun run dev` (client on 5173, server on 8000)
## 🔗 Links
- Live: https://snippetslibrary.com
- Repo: https://github.com/cojocaru-david/snippetslibrary.com

View File

@@ -2,7 +2,7 @@
name: 'TailCI' name: 'TailCI'
description: 'TailCI is a lightweight, fast, and modern web application built with CodeIgniter and styled with Tailwind CSS. It combines the simplicity of CodeIgniters PHP framework with the utility-first power of Tailwind CSS for rapid development and clean design.' description: 'TailCI is a lightweight, fast, and modern web application built with CodeIgniter and styled with Tailwind CSS. It combines the simplicity of CodeIgniters PHP framework with the utility-first power of Tailwind CSS for rapid development and clean design.'
tags: ['codeigniter', 'tailwindcss', 'php'] tags: ['codeigniter', 'tailwindcss', 'php']
image: '../../../public/static/tailci.png' image: '../../../public/static/tailci.webp'
link: 'https://tailci.cojocarudavid.me' link: 'https://tailci.cojocarudavid.me'
startDate: '2025-03-30' startDate: '2025-03-30'
--- ---

View File

@@ -107,7 +107,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
{headings.length > 0 && <TableOfContents headings={headings} />} {headings.length > 0 && <TableOfContents headings={headings} />}
<article class="prose col-start-2 max-w-none"> <article class="prose col-start-2 max-w-none" aria-label="Blog post">
<Content /> <Content />
</article> </article>
@@ -137,7 +137,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Facebook" title="Share on Facebook"
aria-label="Share on Facebook" aria-label="Share on Facebook"
> >
<Button variant="outline" size="icon"> <Button variant="outline" size="icon" aria-label="Share on Facebook">
<Icon name="line-md:facebook" class="size-4" /> <Icon name="line-md:facebook" class="size-4" />
</Button> </Button>
</Link> </Link>
@@ -148,7 +148,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Twitter" title="Share on Twitter"
aria-label="Share on Twitter" aria-label="Share on Twitter"
> >
<Button variant="outline" size="icon"> <Button variant="outline" size="icon" aria-label="Share on Twitter">
<Icon name="line-md:twitter" class="size-4" /> <Icon name="line-md:twitter" class="size-4" />
</Button> </Button>
</Link> </Link>
@@ -159,7 +159,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on LinkedIn" title="Share on LinkedIn"
aria-label="Share on LinkedIn" aria-label="Share on LinkedIn"
> >
<Button variant="outline" size="icon"> <Button variant="outline" size="icon" aria-label="Share on LinkedIn">
<Icon name="line-md:linkedin" class="size-4" /> <Icon name="line-md:linkedin" class="size-4" />
</Button> </Button>
</Link> </Link>
@@ -170,7 +170,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Reddit" title="Share on Reddit"
aria-label="Share on Reddit" aria-label="Share on Reddit"
> >
<Button variant="outline" size="icon"> <Button variant="outline" size="icon" aria-label="Share on Reddit">
<Icon name="line-md:reddit-loop" class="size-4" /> <Icon name="line-md:reddit-loop" class="size-4" />
</Button> </Button>
</Link> </Link>
@@ -181,7 +181,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on WhatsApp" title="Share on WhatsApp"
aria-label="Share on WhatsApp" aria-label="Share on WhatsApp"
> >
<Button variant="outline" size="icon"> <Button variant="outline" size="icon" aria-label="Share on WhatsApp">
<Icon name="mdi:whatsapp" class="size-4" /> <Icon name="mdi:whatsapp" class="size-4" />
</Button> </Button>
</Link> </Link>
@@ -192,7 +192,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Telegram" title="Share on Telegram"
aria-label="Share on Telegram" aria-label="Share on Telegram"
> >
<Button variant="outline" size="icon"> <Button variant="outline" size="icon" aria-label="Share on Telegram">
<Icon name="line-md:telegram" class="size-4" /> <Icon name="line-md:telegram" class="size-4" />
</Button> </Button>
</Link> </Link>
@@ -203,7 +203,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Pinterest" title="Share on Pinterest"
aria-label="Share on Pinterest" aria-label="Share on Pinterest"
> >
<Button variant="outline" size="icon"> <Button variant="outline" size="icon" aria-label="Share on Pinterest">
<Icon name="mdi:pinterest" class="size-4" /> <Icon name="mdi:pinterest" class="size-4" />
</Button> </Button>
</Link> </Link>

View File

@@ -88,16 +88,16 @@ const currentUrl = Astro.url;
<Image <Image
src={project.data.image} src={project.data.image}
alt={project.data.name} alt={project.data.name}
class="col-start-2 mb-8 h-auto w-full rounded-3xl object-cover" class="col-start-2 mb-8 h-[500px] w-full rounded-3xl object-cover"
loading="lazy" loading="lazy"
fetchpriority="low" fetchpriority="low"
width={1800} width={1200}
height={1600} height={800}
/> />
{headings.length > 0 && <TableOfContents headings={headings} />} {headings.length > 0 && <TableOfContents headings={headings} />}
<article class="prose col-start-2 max-w-none"> <article class="prose col-start-2 max-w-none" aria-label="Project">
<Content /> <Content />
</article> </article>
@@ -116,7 +116,7 @@ const currentUrl = Astro.url;
<h3 class="text-base font-semibold">Project Links</h3> <h3 class="text-base font-semibold">Project Links</h3>
<ul class="list-disc pl-4"> <ul class="list-disc pl-4">
<li> <li>
<a href={project.data.link} target="_blank" rel="noopener noreferrer"> <a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link">
{project.data.link} {project.data.link}
</a> </a>
</li> </li>

View File

@@ -58,7 +58,7 @@ const currentUrl = Astro.url;
<div <div
class="group h-full w-full transition-all duration-300 hover:translate-y-[-4px] even:sm:mt-14" class="group h-full w-full transition-all duration-300 hover:translate-y-[-4px] even:sm:mt-14"
> >
<a class="flex flex-col h-full w-full rounded-2xl overflow-hidden bg-card hover:shadow-lg transition-all duration-300 border border-card-foreground/10" href={`/projects/${project.id}`}> <a class="flex flex-col h-full w-full rounded-2xl overflow-hidden bg-card hover:shadow-lg transition-all duration-300 border border-card-foreground/10" href={`/projects/${project.id}`} aria-label="Project link">
<div <div
class="aspect-[16/10] w-full overflow-hidden" class="aspect-[16/10] w-full overflow-hidden"
> >

View File

@@ -1,43 +1,50 @@
import { SITE } from '@/consts' import { SITE } from '@/consts';
import rss from '@astrojs/rss' import rss from '@astrojs/rss';
import type { APIContext } from 'astro' import type { APIRoute } from 'astro';
import { getAllPosts } from '@/lib/data-utils' import { getCollection } from 'astro:content';
export async function GET(context: APIContext) { export const GET: APIRoute = async ({ site }) => {
try { try {
const posts = await getAllPosts() const blogPosts = await getCollection('blog');
const posts = blogPosts
.filter((post) => !post.data.draft)
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
const now = new Date();
const lastBuildDate = posts.length > 0 ? posts[0].data.date : now;
return rss({ return rss({
title: `${SITE.title} - Tech Blog`, title: `${SITE.title} - Tech Blog`,
description: SITE.description, description: SITE.description,
site: context.site ?? SITE.href, site: site ?? SITE.href.replace(/\/$/, ''),
trailingSlash: false, trailingSlash: false,
customData: `
<language>${SITE.locale || 'en-us'}</language>
<lastBuildDate>${lastBuildDate.toUTCString()}</lastBuildDate>
<ttl>60</ttl>
<managingEditor>${SITE.author} (${SITE.email || 'contact@cojocarudavid.me'})</managingEditor>
<webMaster>${SITE.author} (${SITE.email || 'contact@cojocarudavid.me'})</webMaster>
<image>
<url>${new URL('/ogImage.png', site ?? SITE.href).href}</url>
<title>${SITE.title}</title>
<link>${site ?? SITE.href}</link>
</image>
<atom:link href="${new URL('/rss.xml', site ?? SITE.href).href}" rel="self" type="application/rss+xml" />
`.trim(),
xmlns: {
atom: 'http://www.w3.org/2005/Atom'
},
items: posts.map((post) => ({ items: posts.map((post) => ({
title: post.data.title, title: post.data.title,
description: post.data.description, description: post.data.description || '',
pubDate: post.data.date, pubDate: post.data.date,
link: `/blog/${post.id}/`, link: `/blog/${post.id}/`,
categories: post.data.tags || [], categories: post.data.tags || [],
author: post.data.authors ? post.data.authors.join(', ') : SITE.author, author: post.data.authors ? post.data.authors.join(', ') : SITE.author,
customData: `
<language>${SITE.locale}</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
${post.data.tags ? `<category>${post.data.tags.join(', ')}</category>` : ''}
`.trim(),
})), })),
customData: ` });
<language>${SITE.locale}</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
<ttl>60</ttl>
<image>
<url>${new URL('/ogImage.png', SITE.href).toString()}</url>
<title>${SITE.title}</title>
<link>${SITE.href}</link>
</image>
`.trim(),
})
} catch (error) { } catch (error) {
console.error('Error generating RSS feed:', error) console.error('Error generating RSS feed:', error);
return new Response('Error generating RSS feed', { status: 500 }) return new Response('Error generating RSS feed', { status: 500 });
} }
} };

View File

@@ -118,17 +118,5 @@
@apply text-foreground bg-muted border-border rounded-md border px-3 py-1 font-mono text-xs font-medium shadow-sm; @apply text-foreground bg-muted border-border rounded-md border px-3 py-1 font-mono text-xs font-medium shadow-sm;
@apply [&>span[data-line='']>*]:text-(--shiki-light) dark:[&>span[data-line='']>*]:text-(--shiki-dark); @apply [&>span[data-line='']>*]:text-(--shiki-light) dark:[&>span[data-line='']>*]:text-(--shiki-dark);
} }
.katex-display {
@apply my-8 overflow-x-auto overflow-y-hidden py-3 tracking-normal;
@apply [&>span[data-line='']>*]:text-(--shiki-light) dark:[&>span[data-line='']>*]:text-(--shiki-dark);
}
.katex {
@apply text-foreground;
}
.katex * {
@apply text-foreground;
}
} }
} }

View File

@@ -5,6 +5,7 @@ export type Site = {
author: string author: string
locale: string locale: string
location: string location: string
email: string
} }
export type SocialLink = { export type SocialLink = {

29
vercel.json Normal file
View File

@@ -0,0 +1,29 @@
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains; preload"
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self'; font-src 'self';"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "SAMEORIGIN"
},
{
"key": "Referrer-Policy",
"value": "no-referrer-when-downgrade"
}
]
}
]
}