fix: Biome 格式化 + archive.astro 传入缺失的 tags/categories props
小橘 🍊(NEKO Team)
This commit is contained in:
parent
3e5d8b5b58
commit
4ddbe8ac18
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user