+ /* Satori uses tailwind! Create or view a desing using https://og-playground.vercel.app/ */
+ html`
+
+
+
+
+ ${title}
+ ${description}
+
+
+
+
+ `;
+
+type Props = InferGetStaticPropsType
;
+
+/**
+ * Route for dynamic Open Graph images.
+ * This function will generate Open Graph images only if enabled in `config.ts`.
+ *
+ * @returns {Promise} An object containing the GET, getStaticPaths methods for astro.
+ */
+async function getOpenGraphData() {
+ if (siteConfig.postOGImageDynamic) {
+ return {
+ GET: async function GET(context: APIContext) {
+ const {title, description, published, category, tags } = context.props as Props;
+ const svg = await satori(markup(title, published, description, category, tags), ogOptions);
+ const png = new Resvg(svg).render().asPng();
+
+ return new Response(png, {
+ headers: {
+ "Content-Type": "image/png",
+ "Cache-Control": "public, max-age=31536000, immutable",
+ },
+ });
+ },
+ getStaticPaths: async function getStaticPaths() {
+ const posts = await getCollection("posts");
+ const result = posts.filter(({ data }) => !data.draft)
+ .map((post) => ({
+ params: { slug: post.slug },
+ props: {
+ title: post.data.title,
+ description: post.data.description,
+ published: post.data.published,
+ category: post.data.category,
+ tags: post.data.tags,
+ },
+ }));
+ return result
+ }
+ }
+ } else {
+ return { getStaticPaths: {}, GET: {} } ;
+ }
+}
+
+export const { getStaticPaths, GET } = await getOpenGraphData();
diff --git a/src/pages/posts/[...slug].astro b/src/pages/posts/[...slug].astro
index 20bbe8cfd..baaef6eb2 100644
--- a/src/pages/posts/[...slug].astro
+++ b/src/pages/posts/[...slug].astro
@@ -44,7 +44,7 @@ const jsonLd = {
}
---
-
+
id.endsWith(e))) {
+ const buffer = fs.readFileSync(id);
+ return {
+ code: `export default ${JSON.stringify(buffer)}`,
+ map: null,
+ };
+ }
+ },
+ };
+}
\ No newline at end of file
diff --git a/src/types/config.ts b/src/types/config.ts
index ec4cd6a95..b8a2fd7ae 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -1,59 +1,125 @@
import type { LIGHT_MODE, DARK_MODE, AUTO_MODE } from "@constants/constants"
+/** Main configuration for Fuwari. */
export type SiteConfig = {
+ /** Site title. Shown in site tab, site */
title: string
+ /** Site subtitle. Shown in site tab */
subtitle: string
+ /** The language code (e.g., 'en', 'ja', 'zh_CN') */
lang: string
+ /** The site theme hue */
themeColor: {
+ /** Default hue for the theme color, from 0 to 360.
+ * - e.g. red: 0, teal: 200, cyan: 250, pink: 345 */
hue: number
+ /** Hide the theme color picker for visitors */
fixed: boolean
}
+
+ /** The main site banner */
banner: {
+ /** Whether to display the banner */
enable: boolean
+ /** The URL of the banner image
+ * - Relative to the /src directory.
+ * - Relative to the /public directory if it starts with '/' */
+ src: string
+ }
+
+ /** A flag indicating whether to enable the Open Graph image. */
+ siteOGImage: {
+ /** A flag indicating whether to enable the Open Graph image. */
+ enable: boolean,
+ /** The URL of the site OG image
+ * - This image SHOULD be in the public folder.
+ * - Public assets can be referenced directly using a / . */
src: string
}
+ /** A flag indicating whether to enable dynamic Open Graph generation (defaults to false). */
+ postOGImageDynamic: boolean
+
+
+ /** The favicon fo the site */
favicon: Favicon[]
}
+/** Represents the Favicon of the blog */
export type Favicon = {
- src: string
+ /** Path of the favicon, relative to the /public directory */
+ src: string,
+ /** Either 'light' or 'dark'.
+ * - Set only if you have different favicons for light and dark mode */
theme?: 'light' | 'dark'
+ /** Size of the favicon, set only if you have favicons of different sizes */
sizes?: string
}
+/** Integrated link presets */
export enum LinkPreset {
+ /** A link to the home page */
Home = 0,
+ /** A link to the archive page */
Archive = 1,
+ /** A link to the about page */
About = 2,
}
+/** Represents a navbar link */
export type NavBarLink = {
+ /** The text displayed for the link in the navbar */
name: string
+ /** The URL that the link points to.
+ * - Internal links should not include the base path, as it is automatically added */
url: string
+ /** Show an external link icon and will open in a new tab */
external?: boolean
}
+/** Represents the navbar configuration */
export type NavBarConfig = {
+ /** An array of navigation bar links or link presets */
links: (NavBarLink | LinkPreset)[]
}
+/** Represents the main profile/author */
export type ProfileConfig = {
+ /** An optional URL to the user's avatar image
+ * - Relative to the /src directory.
+ * - Relative to the /public directory if it starts with '/'
+ */
avatar?: string
+ /** Author name */
name: string
+ /** An optional bio or description of the author */
bio?: string
+ /** An array of author social media/other links */
links: {
+ /** The name of the resource */
name: string
+ /** The URL of the resource */
url: string
+ /** The icon to be shown for the resource
+ * - Visit https://icones.js.org/ for icon codes
+ * - You will need to install the corresponding icon set if it's not already included
+ * - Examples: 'fa6-brands:twitter', 'fa6-brands:steam', 'fa6-brands:github'
+ *
+ * pnpm add \@iconify-json\/\
+ */
icon: string
}[]
}
+/** Represents the license preference for posts */
export type LicenseConfig = {
+ /** Whether to display the license information at the end of the page */
enable: boolean
+ /** The name of the license */
name: string
+ /** The URL of the license document */
url: string
}