Refactor project structure and update dependencies; improve code formatting and consistency across multiple components and files

This commit is contained in:
KazooTTT
2025-01-08 00:01:03 +08:00
parent 3c8549fd14
commit 11c193786d
16 changed files with 5571 additions and 6577 deletions

11689
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -2,38 +2,38 @@
import ToolSection from './ToolSection.astro'
interface Props {
title: string
categories: {
title: string
sections: {
[key: string]: {
title: string
tools: {
name: string
description: string
href?: string
iconPath?: string
}[]
}
}
}[]
title: string
categories: {
title: string
sections: {
[key: string]: {
title: string
tools: {
name: string
description: string
href?: string
iconPath?: string
}[]
}
}
}[]
}
const { title, categories } = Astro.props
---
<div>
<h2 class='mb-6 text-xl font-bold'>{title}</h2>
{
categories.map((category) => (
<div class='mb-8'>
<h3 class='mb-4 text-lg font-semibold'>{category.title}</h3>
{Object.values(category.sections).map((section) => (
<div class='mb-6'>
<ToolSection title={section.title} tools={section.tools} />
</div>
))}
</div>
))
}
<h2 class='mb-6 text-xl font-bold'>{title}</h2>
{
categories.map((category) => (
<div class='mb-8'>
<h3 class='mb-4 text-lg font-semibold'>{category.title}</h3>
{Object.values(category.sections).map((section) => (
<div class='mb-6'>
<ToolSection title={section.title} tools={section.tools} />
</div>
))}
</div>
))
}
</div>

View File

@ -1,9 +1,6 @@
import GitHubCalendar from "react-github-calendar";
import GitHubCalendar from 'react-github-calendar'
const GithubHotLine = () => {
return (
<GitHubCalendar username='kazoottt' />
);
};
return <GitHubCalendar username='kazoottt' />
}
export default GithubHotLine
export default GithubHotLine

View File

@ -33,17 +33,19 @@ if (imagePath) {
href={href}
target={target}
>
{imageComponent && (
<Image
src={imageComponent()}
alt={altText}
class='h-32 w-full rounded-2xl rounded-bl-none rounded-br-none object-cover'
loading='eager'
/>
)}
<div class='flex flex-col gap-y-2 flex-1 px-5 py-4 text-center justify-center w-full'>
{
imageComponent && (
<Image
src={imageComponent()}
alt={altText}
class='h-32 w-full rounded-2xl rounded-bl-none rounded-br-none object-cover'
loading='eager'
/>
)
}
<div class='flex w-full flex-1 flex-col justify-center gap-y-2 px-5 py-4 text-center'>
<h1 class='text-lg font-medium'>{heading}</h1>
<h2 class='text-muted-foreground line-clamp-2 text-sm'>{subheading}</h2>
<h2 class='line-clamp-2 text-sm text-muted-foreground'>{subheading}</h2>
</div>
<slot />

View File

@ -13,12 +13,14 @@ const { headings } = Astro.props
const toc = generateToc(headings)
---
<aside class='sticky top-20 order-2 -me-28 hidden h-[calc(100vh-6rem)] basis-60 lg:flex lg:flex-col'>
<aside
class='sticky top-20 order-2 -me-28 hidden h-[calc(100vh-6rem)] basis-60 lg:flex lg:flex-col'
>
{
toc.length > 0 && (
<>
<h2 class='mb-4 font-semibold'>TABLE OF CONTENTS</h2>
<ul class='text-card-foreground overflow-y-auto pr-4 max-h-[calc(100vh-10rem)]'>
<ul class='max-h-[calc(100vh-10rem)] overflow-y-auto pr-4 text-card-foreground'>
{toc.map((heading) => (
<TOCHeading heading={heading} />
))}

View File

@ -195,20 +195,22 @@ import { Image } from 'astro:assets'
height='32'
viewBox='0 0 24 24'
class='ml-2 h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:hidden dark:-rotate-90 dark:scale-0'
><path
fill='currentColor'
d='M12 15q1.25 0 2.125-.875T15 12q0-1.25-.875-2.125T12 9q-1.25 0-2.125.875T9 12q0 1.25.875 2.125T12 15m0 1q-1.671 0-2.836-1.164T8 12q0-1.671 1.164-2.836T12 8q1.671 0 2.836 1.164T16 12q0 1.671-1.164 2.836T12 16m-7-3.5H1.5v-1H5zm17.5 0H19v-1h3.5zM11.5 5V1.5h1V5zm0 17.5V19h1v3.5zM6.746 7.404l-2.16-2.098l.695-.744l2.111 2.134zM18.72 19.438l-2.117-2.14l.652-.702l2.16 2.098zM16.596 6.746l2.098-2.16l.744.695l-2.134 2.111zM4.562 18.72l2.14-2.117l.663.652l-2.078 2.179zM12 12'
></path></svg>
><path
fill='currentColor'
d='M12 15q1.25 0 2.125-.875T15 12q0-1.25-.875-2.125T12 9q-1.25 0-2.125.875T9 12q0 1.25.875 2.125T12 15m0 1q-1.671 0-2.836-1.164T8 12q0-1.671 1.164-2.836T12 8q1.671 0 2.836 1.164T16 12q0 1.671-1.164 2.836T12 16m-7-3.5H1.5v-1H5zm17.5 0H19v-1h3.5zM11.5 5V1.5h1V5zm0 17.5V19h1v3.5zM6.746 7.404l-2.16-2.098l.695-.744l2.111 2.134zM18.72 19.438l-2.117-2.14l.652-.702l2.16 2.098zM16.596 6.746l2.098-2.16l.744.695l-2.134 2.111zM4.562 18.72l2.14-2.117l.663.652l-2.078 2.179zM12 12'
></path></svg
>
<svg
xmlns='http://www.w3.org/2000/svg'
width='32'
height='32'
viewBox='0 0 24 24'
class='ml-2 hidden h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:block dark:rotate-0 dark:scale-100'
><path
fill='currentColor'
d='M12.058 20q-3.334 0-5.667-2.333Q4.058 15.333 4.058 12q0-3.038 1.98-5.27Q8.02 4.5 10.942 4.097q.081 0 .159.006t.153.017q-.506.706-.801 1.57q-.295.865-.295 1.811q0 2.667 1.866 4.533q1.867 1.867 4.534 1.867q.952 0 1.813-.295q.862-.295 1.548-.801q.012.075.018.153q.005.078.005.158q-.384 2.923-2.615 4.904T12.057 20'
></path></svg>
><path
fill='currentColor'
d='M12.058 20q-3.334 0-5.667-2.333Q4.058 15.333 4.058 12q0-3.038 1.98-5.27Q8.02 4.5 10.942 4.097q.081 0 .159.006t.153.017q-.506.706-.801 1.57q-.295.865-.295 1.811q0 2.667 1.866 4.533q1.867 1.867 4.534 1.867q.952 0 1.813-.295q.862-.295 1.548-.801q.012.075.018.153q.005.078.005.158q-.384 2.923-2.615 4.904T12.057 20'
></path></svg
>
</button>
</div>
</div>
@ -223,7 +225,7 @@ import { Image } from 'astro:assets'
function setupDarkModeToggle() {
const toggleDarkModeButton = document.getElementById('toggleDarkMode')
const mobileToggleDarkMode = document.getElementById('mobileToggleDarkMode')
function toggleTheme() {
const currentTheme = getCurrentTheme()
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'

View File

@ -23,7 +23,14 @@ const articleDate = date?.toISOString()
const { headings } = await post.render()
---
<PageLayout meta={{ articleDate, description: description ?? '', ogImage: socialImage, title }}>
<PageLayout
meta={{
articleDate,
description: description ?? '',
ogImage: socialImage,
title
}}
>
<div class='w-full'>
<Button title='Back' href={backHref} style='button'>
<svg

View File

@ -1,163 +1,176 @@
import type { APIRoute } from 'astro';
import type { APIRoute } from 'astro'
export const GET: APIRoute = async ({ params, locals, request }) => {
// Handle multiple path segments and decode the URL-encoded slug
const slugSegments = params.slug?.split('/') || [];
const slug = decodeURIComponent(slugSegments.join('/'));
if (!slug) {
return new Response(JSON.stringify({ error: 'Slug is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// Handle multiple path segments and decode the URL-encoded slug
const slugSegments = params.slug?.split('/') || []
const slug = decodeURIComponent(slugSegments.join('/'))
// Access runtime environment through context
const ctx = locals as any;
const env = ctx.env || ctx.runtime?.env;
console.log('Context:', {
hasEnv: !!ctx.env,
hasRuntime: !!ctx.runtime,
envKeys: ctx.env ? Object.keys(ctx.env) : [],
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
});
if (!slug) {
return new Response(JSON.stringify({ error: 'Slug is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
})
}
const db = env?.DB;
if (!db) {
console.error('D1 database not available');
return new Response(
JSON.stringify({
error: 'Database not configured',
context: {
hasEnv: !!ctx.env,
hasRuntime: !!ctx.runtime,
envKeys: ctx.env ? Object.keys(ctx.env) : [],
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
}
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
// Access runtime environment through context
const ctx = locals as any
const env = ctx.env || ctx.runtime?.env
try {
// Get current view count
const { results } = await db
.prepare('SELECT views FROM pageviews WHERE slug = ?')
.bind(slug)
.all();
console.log('Context:', {
hasEnv: !!ctx.env,
hasRuntime: !!ctx.runtime,
envKeys: ctx.env ? Object.keys(ctx.env) : [],
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
})
const views = results.length > 0 ? results[0].views : 0;
const db = env?.DB
if (!db) {
console.error('D1 database not available')
return new Response(
JSON.stringify({
error: 'Database not configured',
context: {
hasEnv: !!ctx.env,
hasRuntime: !!ctx.runtime,
envKeys: ctx.env ? Object.keys(ctx.env) : [],
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
}
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
)
}
return new Response(JSON.stringify({ views }), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error('Error getting page views:', error);
return new Response(
JSON.stringify({
error: 'Failed to get page views',
details: error instanceof Error ? error.message : String(error)
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}
};
try {
// Get current view count
const { results } = await db
.prepare('SELECT views FROM pageviews WHERE slug = ?')
.bind(slug)
.all()
const views = results.length > 0 ? results[0].views : 0
return new Response(JSON.stringify({ views }), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
})
} catch (error) {
console.error('Error getting page views:', error)
return new Response(
JSON.stringify({
error: 'Failed to get page views',
details: error instanceof Error ? error.message : String(error)
}),
{
status: 500,
headers: {
'Content-Type': 'application/json'
}
}
)
}
}
export const POST: APIRoute = async ({ params, locals, request }) => {
// Handle multiple path segments and decode the URL-encoded slug
const slugSegments = params.slug?.split('/') || [];
const slug = decodeURIComponent(slugSegments.join('/'));
if (!slug) {
return new Response(JSON.stringify({ error: 'Slug is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// Handle multiple path segments and decode the URL-encoded slug
const slugSegments = params.slug?.split('/') || []
const slug = decodeURIComponent(slugSegments.join('/'))
// Access runtime environment through context
const ctx = locals as any;
const env = ctx.env || ctx.runtime?.env;
console.log('Context:', {
hasEnv: !!ctx.env,
hasRuntime: !!ctx.runtime,
envKeys: ctx.env ? Object.keys(ctx.env) : [],
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
});
if (!slug) {
return new Response(JSON.stringify({ error: 'Slug is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
})
}
const db = env?.DB;
if (!db) {
console.error('D1 database not available');
return new Response(
JSON.stringify({
error: 'Database not configured',
context: {
hasEnv: !!ctx.env,
hasRuntime: !!ctx.runtime,
envKeys: ctx.env ? Object.keys(ctx.env) : [],
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
}
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
// Access runtime environment through context
const ctx = locals as any
const env = ctx.env || ctx.runtime?.env
try {
// Get request information
const ip_address = request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for') || 'unknown';
const user_agent = request.headers.get('user-agent') || 'unknown';
const referrer = request.headers.get('referer') || 'unknown';
console.log('Context:', {
hasEnv: !!ctx.env,
hasRuntime: !!ctx.runtime,
envKeys: ctx.env ? Object.keys(ctx.env) : [],
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
})
// Start a transaction to ensure both operations succeed or fail together
const stmt1 = db.prepare(
`INSERT INTO pageviews (slug, views)
const db = env?.DB
if (!db) {
console.error('D1 database not available')
return new Response(
JSON.stringify({
error: 'Database not configured',
context: {
hasEnv: !!ctx.env,
hasRuntime: !!ctx.runtime,
envKeys: ctx.env ? Object.keys(ctx.env) : [],
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
}
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
)
}
try {
// Get request information
const ip_address =
request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for') || 'unknown'
const user_agent = request.headers.get('user-agent') || 'unknown'
const referrer = request.headers.get('referer') || 'unknown'
// Start a transaction to ensure both operations succeed or fail together
const stmt1 = db
.prepare(
`INSERT INTO pageviews (slug, views)
VALUES (?, 1)
ON CONFLICT(slug)
DO UPDATE SET views = views + 1,
updated_at = CURRENT_TIMESTAMP`
).bind(slug);
)
.bind(slug)
const stmt2 = db.prepare(
`INSERT INTO pageview_logs (slug, ip_address, user_agent, referrer)
const stmt2 = db
.prepare(
`INSERT INTO pageview_logs (slug, ip_address, user_agent, referrer)
VALUES (?, ?, ?, ?)`
).bind(slug, ip_address, user_agent, referrer);
)
.bind(slug, ip_address, user_agent, referrer)
// Execute both statements in a transaction
await db.batch([stmt1, stmt2]);
// Execute both statements in a transaction
await db.batch([stmt1, stmt2])
// Get updated view count
const { results } = await db
.prepare('SELECT views FROM pageviews WHERE slug = ?')
.bind(slug)
.all();
// Get updated view count
const { results } = await db
.prepare('SELECT views FROM pageviews WHERE slug = ?')
.bind(slug)
.all()
return new Response(JSON.stringify({ views: results[0].views }), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error('Error updating page views:', error);
return new Response(
JSON.stringify({
error: 'Failed to update page views',
details: error instanceof Error ? error.message : String(error)
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}
};
return new Response(JSON.stringify({ views: results[0].views }), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
})
} catch (error) {
console.error('Error updating page views:', error)
return new Response(
JSON.stringify({
error: 'Failed to update page views',
details: error instanceof Error ? error.message : String(error)
}),
{
status: 500,
headers: {
'Content-Type': 'application/json'
}
}
)
}
}

View File

@ -20,7 +20,10 @@ export const getStaticPaths = (async ({ paginate }) => {
const allPostsByDate = sortMDByDate(allPosts)
const uniqueTags = getUniqueTagsWithCount(allPosts)
const uniqueCategories = getUniqueCategoriesWithCount(allPosts)
return paginate(allPostsByDate, { pageSize: 50, props: { uniqueTags, uniqueCategories } })
return paginate(allPostsByDate, {
pageSize: 50,
props: { uniqueTags, uniqueCategories }
})
}) satisfies GetStaticPaths
interface Props {

View File

@ -5,7 +5,6 @@ import type { GetStaticPaths, InferGetStaticPropsType } from 'astro'
import PostLayout from '@/layouts/BlogPost.astro'
import { getAllPosts } from '@/utils'
import GiscusComment from '@/components/GiscusComment'
export const getStaticPaths = (async () => {
const blogEntries = await getAllPosts()
@ -21,7 +20,6 @@ const { entry } = Astro.props
const { Content } = await entry.render()
---
<PostLayout post={entry}>
<PostLayout post={entry} backHref='/blog'>
<Content />
<GiscusComment client:load />
</PostLayout>

View File

@ -7,7 +7,7 @@ const allPosts = await getAllPosts()
const allCategories = getUniqueCategoriesWithCount(allPosts)
const meta = {
description: 'A list of all the topics I\'ve written about in my posts',
description: "A list of all the topics I've written about in my posts",
title: 'All Categories'
}
---

View File

@ -4,7 +4,6 @@ export const prerender = true
import type { GetStaticPaths, InferGetStaticPropsType } from 'astro'
import PostLayout from '@/layouts/BlogPost.astro'
import GiscusComment from '@/components/GiscusComment'
import { getallDiaries } from 'src/utils'
export const getStaticPaths = (async () => {
@ -23,5 +22,4 @@ const { Content } = await entry.render()
<PostLayout post={entry} simple={true} backHref='/diary'>
<Content />
<GiscusComment client:load />
</PostLayout>

View File

@ -202,7 +202,7 @@ const projects = [...directProjects, ...mdProjects].slice(0, MAX_PROJECTS)
</div>
<div class='mt-4 flex justify-end'>
<a
href={`/categories/${encodeURIComponent('项目-已结项')}`}
href={`/categories/${encodeURIComponent('项目')}`}
class='flex items-center gap-x-1 text-sm text-muted-foreground transition-colors hover:text-foreground'
>
查看更多项目

View File

@ -1,5 +1,5 @@
import type { SiteConfig } from '@/types';
import type { AstroExpressiveCodeOptions } from 'astro-expressive-code';
import type { SiteConfig } from '@/types'
import type { AstroExpressiveCodeOptions } from 'astro-expressive-code'
export const siteConfig: SiteConfig = {
// Used as both a meta property (src/components/BaseHead.astro L:31 + L:49) & the generated satori png (src/pages/og-image/[slug].png.ts)
@ -21,7 +21,7 @@ export const siteConfig: SiteConfig = {
year: 'numeric'
}
},
site: 'https://blog.kazoottt.top',
site: 'https://blog.kazoottt.top'
}
export const menuLinks: Array<{ title: string; path: string }> = [

View File

@ -24,10 +24,9 @@ export type SiteMeta = {
articleDate?: string | undefined
}
export interface CategoryHierarchy {
category: string
fullCategory: string
children: Record<string, CategoryHierarchy>
count: number
}
}

View File

@ -3,11 +3,15 @@ export { elementHasClass, rootInDarkMode, toggleClass } from './domElement'
export { generateToc } from './generateToc'
export type { TocItem } from './generateToc'
export {
getAllCategories, getAllPosts,
getAllSortedPosts, getUniqueCategories,
getUniqueCategoriesWithCount, getUniqueTags,
getUniqueTagsWithCount, getAllDiaries as getallDiaries,
getAllDiariesSorted as getallDiariesSorted, sortMDByDate
getAllCategories,
getAllPosts,
getAllSortedPosts,
getUniqueCategories,
getUniqueCategoriesWithCount,
getUniqueTags,
getUniqueTagsWithCount,
getAllDiaries as getallDiaries,
getAllDiariesSorted as getallDiariesSorted,
sortMDByDate
} from './post'
export { cn } from './tailwind'