diff --git a/.vscode/settings.json b/.vscode/settings.json index 685e0e2..2d1be0d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -67,6 +67,7 @@ "Beze", "blurhash", "bmoji", + "canvaskit", "Disqus", "Etiquetas", "Frontmatter", diff --git a/package.json b/package.json index 945f4fe..918f880 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "@rehype-pretty/transformers": "^0.13.2", "astro": "^5.4.3", "astro-compress": "^2.3.6", + "astro-og-canvas": "^0.7.0", "astro-robots-txt": "^1.0.0", + "canvaskit-wasm": "^0.39.1", "hastscript": "^9.0.1", "markdown-it": "^14.1.0", "overlayscrollbars": "^2.11.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb922a6..89c69d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,15 @@ importers: astro-compress: specifier: ^2.3.6 version: 2.3.6(@types/node@22.13.10)(jiti@2.4.2)(rollup@4.35.0)(typescript@5.8.2)(yaml@2.7.0) + astro-og-canvas: + specifier: ^0.7.0 + version: 0.7.0(astro@5.4.3(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.28.2)(rollup@4.35.0)(terser@5.37.0)(typescript@5.8.2)(yaml@2.7.0)) astro-robots-txt: specifier: ^1.0.0 version: 1.0.0 + canvaskit-wasm: + specifier: ^0.39.1 + version: 0.39.1 hastscript: specifier: ^9.0.1 version: 9.0.1 @@ -1173,6 +1179,9 @@ packages: resolution: {integrity: sha512-K+mfUpuLilzFUN3G84ytB02v5EkaaU0eOyN6BILcSozjUpSsggbgcy73WBiuUVg8u7pRsIvx192/PclJYvaWCg==} engines: {node: '>=18'} + '@webgpu/types@0.1.21': + resolution: {integrity: sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1248,6 +1257,12 @@ packages: resolution: {integrity: sha512-3oqANMjrvJ+IE5pwlUWsH/4UztmYf/GTL0HPUkWnYBNAHiGVGrOh2EbegxS5niAwlO0w9dRYk0CkCPlJcu8c3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + astro-og-canvas@0.7.0: + resolution: {integrity: sha512-aamARDNDORPxXlPt+VJakzj4WpNUDjQqUZ06eE82pGMad4aTR/KMm8Ho12qvprgJcM8K7fgSOMg5OHa7KcX0pw==} + engines: {node: '>=18.14.1'} + peerDependencies: + astro: ^3.0.0 || ^4.0.0 || ^5.0.0 + astro-robots-txt@1.0.0: resolution: {integrity: sha512-6JQSLid4gMhoWjOm85UHLkgrw0+hHIjnJVIUqxjU2D6feKlVyYukMNYjH44ZDZBK1P8hNxd33PgWlHzCASvedA==} @@ -1329,6 +1344,9 @@ packages: caniuse-lite@1.0.30001703: resolution: {integrity: sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==} + canvaskit-wasm@0.39.1: + resolution: {integrity: sha512-Gy3lCmhUdKq+8bvDrs9t8+qf7RvcjuQn+we7vTVVyqgOVO1UVfHpsnBxkTZw+R4ApEJ3D5fKySl9TU11hmjl/A==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -4920,6 +4938,8 @@ snapshots: transitivePeerDependencies: - typescript + '@webgpu/types@0.1.21': {} + acorn-jsx@5.3.2(acorn@8.14.1): dependencies: acorn: 8.14.1 @@ -5042,6 +5062,13 @@ snapshots: transitivePeerDependencies: - supports-color + astro-og-canvas@0.7.0(astro@5.4.3(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.28.2)(rollup@4.35.0)(terser@5.37.0)(typescript@5.8.2)(yaml@2.7.0)): + dependencies: + astro: 5.4.3(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.28.2)(rollup@4.35.0)(terser@5.37.0)(typescript@5.8.2)(yaml@2.7.0) + canvaskit-wasm: 0.39.1 + deterministic-object-hash: 2.0.2 + entities: 4.5.0 + astro-robots-txt@1.0.0: dependencies: valid-filename: 4.0.0 @@ -5210,6 +5237,10 @@ snapshots: caniuse-lite@1.0.30001703: {} + canvaskit-wasm@0.39.1: + dependencies: + '@webgpu/types': 0.1.21 + ccount@2.0.1: {} chalk@4.1.2: diff --git a/src/content/config.ts b/src/content/config.ts index cce7946..bded872 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -2,26 +2,22 @@ import { defineCollection, z } from 'astro:content' const postsCollection = defineCollection({ schema: z.object({ - - // Basic + // required title: z.string(), published: z.date(), + // optional updated: z.date().optional(), + description: z.string().optional().default(''), tags: z.array(z.string()).optional().default([]), - // Advanced - pin: z.number().int().min(0).max(99).optional().default(0), draft: z.boolean().optional().default(false), + pin: z.number().int().min(0).max(99).optional().default(0), toc: z.boolean().optional().default(false), lang: z.string().optional().default(''), abbrlink: z.string().optional().default('').refine( abbrlink => !abbrlink || /^[a-z0-9\-]*$/.test(abbrlink), { message: 'Abbrlink can only contain lowercase letters, numbers and hyphens' }, ), - - // Auto-generated - description: z.string().optional().default(''), - image: z.string().optional().default(''), }), }) diff --git a/src/content/posts/Universal Post.md b/src/content/posts/Universal Article.md similarity index 100% rename from src/content/posts/Universal Post.md rename to src/content/posts/Universal Article.md diff --git a/src/layouts/Head.astro b/src/layouts/Head.astro index 5cbccae..3aa9695 100644 --- a/src/layouts/Head.astro +++ b/src/layouts/Head.astro @@ -1,14 +1,14 @@ --- import themeConfig from '@/config' +import { isPostPage } from '@/utils/path' import { ClientRouter } from 'astro:transitions' interface Props { postTitle?: string postDescription?: string - postImage?: string } -const { postTitle, postDescription, postImage } = Astro.props +const { postTitle, postDescription } = Astro.props const { title, subtitle, description, author, url, favicon } = themeConfig.site const { mode, light: { background: lightMode }, dark: { background: darkMode } } = themeConfig.color @@ -20,7 +20,18 @@ const { commentURL = '', imageHostURL = '', customGoogleAnalyticsJS = '', custom const initMetaTheme = mode === 'dark' ? darkMode : lightMode const pageTitle = postTitle ? `${postTitle} | ${title}` : `${title} - ${subtitle}` const pageDescription = postDescription || description -const pageImage = postImage || favicon + +// Determine the OG image for the page +// Check if the current page is a post +const currentPath = Astro.url.pathname +const isPost = isPostPage(currentPath) + +// Extract slug from URL as the post identifier +const pathParts = currentPath.split('/').filter(part => part !== '') +const slug = pathParts.length > 0 ? pathParts[pathParts.length - 1] : '' + +// Prioritize auto-generated OG image, otherwise use fallback OG image +const pageImage = isPost && slug ? `${url}/og/${slug}` : favicon --- diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 8a1ce51..26be3bb 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -18,10 +18,9 @@ import '@/styles/heti.css' interface Props { postTitle?: string postDescription?: string - postImage?: string } -const { postTitle, postDescription, postImage } = Astro.props +const { postTitle, postDescription } = Astro.props const { isHome, isPost } = getPagePath(Astro.url.pathname) const fontStyle = themeConfig.global.fontStyle === 'serif' ? 'font-serif' : 'font-sans' @@ -35,7 +34,7 @@ const footerMarginClass = isPost && themeConfig.comment?.waline?.serverURL class={fontStyle} data-overlayscrollbars-initialize > - +
[ + data.abbrlink || slug, // Prioritize using abbrlink instead of slug + { + title: data.title, + description: data.description || '', + }, + ]), +) + +// Configure Open Graph image generation route +export const { getStaticPaths, GET } = OGImageRoute({ + param: 'route', + pages, + getImageOptions: (_path, page) => ({ + title: page.title, + description: page.description, + border: { width: 10 }, + padding: 40, + }), +}) diff --git a/src/pages/posts/[slug].astro b/src/pages/posts/[slug].astro index 997d5dc..71ee590 100644 --- a/src/pages/posts/[slug].astro +++ b/src/pages/posts/[slug].astro @@ -26,7 +26,6 @@ const { Content, remarkPluginFrontmatter } = await post.render()