mirror of
https://github.com/KazooTTT/kazoottt-blog.git
synced 2025-06-16 23:41:21 +08:00
Refactor project structure and update dependencies; improve code formatting and consistency across multiple components and files
This commit is contained in:
11461
pnpm-lock.yaml
generated
11461
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -33,17 +33,19 @@ if (imagePath) {
|
||||
href={href}
|
||||
target={target}
|
||||
>
|
||||
{imageComponent && (
|
||||
{
|
||||
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'>
|
||||
)
|
||||
}
|
||||
<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 />
|
||||
|
@ -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} />
|
||||
))}
|
||||
|
@ -198,7 +198,8 @@ import { Image } from 'astro:assets'
|
||||
><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></svg
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='32'
|
||||
@ -208,7 +209,8 @@ import { Image } from 'astro:assets'
|
||||
><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></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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
|
||||
|
@ -1,31 +1,31 @@
|
||||
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('/'));
|
||||
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' },
|
||||
});
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
|
||||
// Access runtime environment through context
|
||||
const ctx = locals as any;
|
||||
const env = ctx.env || ctx.runtime?.env;
|
||||
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) : []
|
||||
});
|
||||
})
|
||||
|
||||
const db = env?.DB;
|
||||
const db = env?.DB
|
||||
if (!db) {
|
||||
console.error('D1 database not available');
|
||||
console.error('D1 database not available')
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Database not configured',
|
||||
@ -35,10 +35,12 @@ export const GET: APIRoute = async ({ params, locals, request }) => {
|
||||
envKeys: ctx.env ? Object.keys(ctx.env) : [],
|
||||
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
|
||||
}
|
||||
}), {
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
@ -46,57 +48,59 @@ export const GET: APIRoute = async ({ params, locals, request }) => {
|
||||
const { results } = await db
|
||||
.prepare('SELECT views FROM pageviews WHERE slug = ?')
|
||||
.bind(slug)
|
||||
.all();
|
||||
.all()
|
||||
|
||||
const views = results.length > 0 ? results[0].views : 0;
|
||||
const views = results.length > 0 ? results[0].views : 0
|
||||
|
||||
return new Response(JSON.stringify({ views }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error getting page views:', 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',
|
||||
},
|
||||
});
|
||||
'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('/'));
|
||||
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' },
|
||||
});
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
|
||||
// Access runtime environment through context
|
||||
const ctx = locals as any;
|
||||
const env = ctx.env || ctx.runtime?.env;
|
||||
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) : []
|
||||
});
|
||||
})
|
||||
|
||||
const db = env?.DB;
|
||||
const db = env?.DB
|
||||
if (!db) {
|
||||
console.error('D1 database not available');
|
||||
console.error('D1 database not available')
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Database not configured',
|
||||
@ -106,58 +110,67 @@ export const POST: APIRoute = async ({ params, locals, request }) => {
|
||||
envKeys: ctx.env ? Object.keys(ctx.env) : [],
|
||||
runtimeEnvKeys: ctx.runtime?.env ? Object.keys(ctx.runtime.env) : []
|
||||
}
|
||||
}), {
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
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';
|
||||
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(
|
||||
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(
|
||||
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]);
|
||||
await db.batch([stmt1, stmt2])
|
||||
|
||||
// Get updated view count
|
||||
const { results } = await db
|
||||
.prepare('SELECT views FROM pageviews WHERE slug = ?')
|
||||
.bind(slug)
|
||||
.all();
|
||||
.all()
|
||||
|
||||
return new Response(JSON.stringify({ views: results[0].views }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error updating page views:', 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',
|
||||
},
|
||||
});
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
}
|
||||
---
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
>
|
||||
查看更多项目
|
||||
|
@ -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 }> = [
|
||||
|
@ -24,7 +24,6 @@ export type SiteMeta = {
|
||||
articleDate?: string | undefined
|
||||
}
|
||||
|
||||
|
||||
export interface CategoryHierarchy {
|
||||
category: string
|
||||
fullCategory: string
|
||||
|
@ -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'
|
||||
|
||||
|
Reference in New Issue
Block a user