fix: Biome 格式化 + archive.astro 传入缺失的 tags/categories props

小橘 🍊(NEKO Team)
This commit is contained in:
小橘 2026-04-15 05:51:21 +00:00
parent 3e5d8b5b58
commit 4ddbe8ac18
4 changed files with 238 additions and 230 deletions

View File

@ -29,7 +29,8 @@ interface Props {
ogImage?: string; ogImage?: string;
} }
let { title, banner, description, lang, setOGTypeArticle, ogImage } = Astro.props; let { title, banner, description, lang, setOGTypeArticle, ogImage } =
Astro.props;
// apply a class to the body element to decide the height of the banner, only used for initial page load // apply a class to the body element to decide the height of the banner, only used for initial page load
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change // Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change

View File

@ -3,12 +3,18 @@ import ArchivePanel from "@components/ArchivePanel.svelte";
import I18nKey from "@i18n/i18nKey"; import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation"; import { i18n } from "@i18n/translation";
import MainGridLayout from "@layouts/MainGridLayout.astro"; import MainGridLayout from "@layouts/MainGridLayout.astro";
import { getSortedPostsList } from "../utils/content-utils"; import {
getCategoryList,
getSortedPostsList,
getTagList,
} from "../utils/content-utils";
const sortedPostsList = await getSortedPostsList(); const sortedPostsList = await getSortedPostsList();
const tags = (await getTagList()).map((t) => t.name);
const categories = (await getCategoryList()).map((c) => c.name);
--- ---
<MainGridLayout title={i18n(I18nKey.archive)}> <MainGridLayout title={i18n(I18nKey.archive)}>
<ArchivePanel sortedPosts={sortedPostsList} client:only="svelte"></ArchivePanel> <ArchivePanel sortedPosts={sortedPostsList} tags={tags} categories={categories} client:only="svelte"></ArchivePanel>
</MainGridLayout> </MainGridLayout>

View File

@ -1,246 +1,247 @@
import type { APIRoute, GetStaticPaths } from "astro";
import { getSortedPosts } from "@utils/content-utils";
import satori from "satori";
import sharp from "sharp";
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import { getSortedPosts } from "@utils/content-utils";
import type { APIRoute, GetStaticPaths } from "astro";
import satori from "satori";
import sharp from "sharp";
import { formatDateToYYYYMMDD } from "../../utils/date-utils"; import { formatDateToYYYYMMDD } from "../../utils/date-utils";
// Load font at module level (cached across calls during build) // Load font at module level (cached across calls during build)
const fontPath = path.resolve("src/assets/fonts/NotoSansSC-Regular.ttf"); const fontPath = path.resolve("src/assets/fonts/NotoSansSC-Regular.ttf");
let fontData: ArrayBuffer; let fontData: ArrayBuffer;
try { try {
fontData = fs.readFileSync(fontPath).buffer as ArrayBuffer; fontData = fs.readFileSync(fontPath).buffer as ArrayBuffer;
} catch { } catch {
// Font will be downloaded by build script; fail gracefully if missing // Font will be downloaded by build script; fail gracefully if missing
fontData = new ArrayBuffer(0); fontData = new ArrayBuffer(0);
} }
export const getStaticPaths: GetStaticPaths = async () => { export const getStaticPaths: GetStaticPaths = async () => {
const posts = await getSortedPosts(); const posts = await getSortedPosts();
return posts.map((post) => ({ return posts.map((post) => ({
params: { slug: post.slug }, params: { slug: post.slug },
props: { props: {
title: post.data.title, title: post.data.title,
description: post.data.description || "", description: post.data.description || "",
date: formatDateToYYYYMMDD(post.data.published), date: formatDateToYYYYMMDD(post.data.published),
tags: post.data.tags || [], tags: post.data.tags || [],
}, },
})); }));
}; };
export const GET: APIRoute = async ({ props }) => { export const GET: APIRoute = async ({ props }) => {
const { title, description, date, tags } = props as { const { title, description, date, tags } = props as {
title: string; title: string;
description: string; description: string;
date: string; date: string;
tags: string[]; tags: string[];
}; };
// Truncate title to ~40 chars for display // Truncate title to ~40 chars for display
const displayTitle = const displayTitle = title.length > 42 ? `${title.slice(0, 40)}` : title;
title.length > 42 ? title.slice(0, 40) + "…" : title; const displayDesc =
const displayDesc = description.length > 70 ? `${description.slice(0, 68)}` : description;
description.length > 70 ? description.slice(0, 68) + "…" : description; const displayTags = tags.slice(0, 3).join(" · ");
const displayTags = tags.slice(0, 3).join(" · ");
const svg = await satori( const svg = await satori(
{ {
type: "div", type: "div",
props: { props: {
style: { style: {
width: "100%", width: "100%",
height: "100%", height: "100%",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
justifyContent: "space-between", justifyContent: "space-between",
padding: "60px 64px", padding: "60px 64px",
background: "linear-gradient(135deg, #0f172a 0%, #1e3a5f 40%, #3b82f6 100%)", background:
color: "#ffffff", "linear-gradient(135deg, #0f172a 0%, #1e3a5f 40%, #3b82f6 100%)",
fontFamily: "Noto Sans SC", color: "#ffffff",
}, fontFamily: "Noto Sans SC",
children: [ },
// Top: logo + branding children: [
{ // Top: logo + branding
type: "div", {
props: { type: "div",
style: { props: {
display: "flex", style: {
alignItems: "center", display: "flex",
gap: "12px", alignItems: "center",
}, gap: "12px",
children: [ },
{ children: [
type: "span", {
props: { type: "span",
style: { fontSize: "36px" }, props: {
children: "🍊", style: { fontSize: "36px" },
}, children: "🍊",
}, },
{ },
type: "span", {
props: { type: "span",
style: { props: {
fontSize: "22px", style: {
color: "rgba(255,255,255,0.7)", fontSize: "22px",
letterSpacing: "0.05em", color: "rgba(255,255,255,0.7)",
}, letterSpacing: "0.05em",
children: "小橘的日记", },
}, children: "小橘的日记",
}, },
], },
}, ],
}, },
// Middle: title + description },
{ // Middle: title + description
type: "div", {
props: { type: "div",
style: { props: {
display: "flex", style: {
flexDirection: "column", display: "flex",
gap: "16px", flexDirection: "column",
flex: "1", gap: "16px",
justifyContent: "center", flex: "1",
}, justifyContent: "center",
children: [ },
{ children: [
type: "div", {
props: { type: "div",
style: { props: {
fontSize: "48px", style: {
fontWeight: 700, fontSize: "48px",
lineHeight: 1.3, fontWeight: 700,
letterSpacing: "-0.02em", lineHeight: 1.3,
textShadow: "0 2px 10px rgba(0,0,0,0.3)", letterSpacing: "-0.02em",
}, textShadow: "0 2px 10px rgba(0,0,0,0.3)",
children: displayTitle, },
}, children: displayTitle,
}, },
description },
? { description
type: "div", ? {
props: { type: "div",
style: { props: {
fontSize: "22px", style: {
color: "rgba(255,255,255,0.65)", fontSize: "22px",
lineHeight: 1.5, color: "rgba(255,255,255,0.65)",
}, lineHeight: 1.5,
children: displayDesc, },
}, children: displayDesc,
} },
: null, }
].filter(Boolean), : null,
}, ].filter(Boolean),
}, },
// Bottom: date + tags + site },
{ // Bottom: date + tags + site
type: "div", {
props: { type: "div",
style: { props: {
display: "flex", style: {
justifyContent: "space-between", display: "flex",
alignItems: "flex-end", justifyContent: "space-between",
}, alignItems: "flex-end",
children: [ },
{ children: [
type: "div", {
props: { type: "div",
style: { props: {
display: "flex", style: {
flexDirection: "column", display: "flex",
gap: "6px", flexDirection: "column",
}, gap: "6px",
children: [ },
displayTags children: [
? { displayTags
type: "div", ? {
props: { type: "div",
style: { props: {
fontSize: "16px", style: {
color: "rgba(255,255,255,0.5)", fontSize: "16px",
}, color: "rgba(255,255,255,0.5)",
children: displayTags, },
}, children: displayTags,
} },
: null, }
{ : null,
type: "div", {
props: { type: "div",
style: { props: {
fontSize: "18px", style: {
color: "rgba(255,255,255,0.6)", fontSize: "18px",
}, color: "rgba(255,255,255,0.6)",
children: date, },
}, children: date,
}, },
].filter(Boolean), },
}, ].filter(Boolean),
}, },
{ },
type: "div", {
props: { type: "div",
style: { props: {
display: "flex", style: {
alignItems: "center", display: "flex",
gap: "8px", alignItems: "center",
}, gap: "8px",
children: [ },
{ children: [
type: "span", {
props: { type: "span",
style: { props: {
fontSize: "24px", style: {
}, fontSize: "24px",
children: "✨ 🌙 ☁️", },
}, children: "✨ 🌙 ☁️",
}, },
{ },
type: "span", {
props: { type: "span",
style: { props: {
fontSize: "16px", style: {
color: "rgba(255,255,255,0.4)", fontSize: "16px",
}, color: "rgba(255,255,255,0.4)",
children: "oc-xiaoju.github.io", },
}, children: "oc-xiaoju.github.io",
}, },
], },
}, ],
}, },
], },
}, ],
}, },
], },
}, ],
}, },
{ },
width: 1200, {
height: 630, width: 1200,
fonts: fontData.byteLength > 0 height: 630,
? [ fonts:
{ fontData.byteLength > 0
name: "Noto Sans SC", ? [
data: fontData, {
weight: 400 as const, name: "Noto Sans SC",
style: "normal" as const, data: fontData,
}, weight: 400 as const,
{ style: "normal" as const,
name: "Noto Sans SC", },
data: fontData, // variable font covers all weights {
weight: 700 as const, name: "Noto Sans SC",
style: "normal" as const, data: fontData, // variable font covers all weights
}, weight: 700 as const,
] style: "normal" as const,
: [], },
} ]
); : [],
},
);
const png = await sharp(Buffer.from(svg)).png().toBuffer(); const png = await sharp(Buffer.from(svg)).png().toBuffer();
return new Response(png as unknown as BodyInit, { return new Response(png as unknown as BodyInit, {
headers: { headers: {
"Content-Type": "image/png", "Content-Type": "image/png",
"Cache-Control": "public, max-age=31536000, immutable", "Cache-Control": "public, max-age=31536000, immutable",
}, },
}); });
}; };

View File

@ -1,7 +1,7 @@
--- ---
import path from "node:path"; import path from "node:path";
import License from "@components/misc/License.astro";
import Comments from "@components/misc/Comments.astro"; import Comments from "@components/misc/Comments.astro";
import License from "@components/misc/License.astro";
import Markdown from "@components/misc/Markdown.astro"; import Markdown from "@components/misc/Markdown.astro";
import I18nKey from "@i18n/i18nKey"; import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation"; import { i18n } from "@i18n/translation";