From 6c36bde9f1d517a224144655e7a90af047f5068e Mon Sep 17 00:00:00 2001 From: satnaing Date: Sat, 24 Dec 2022 21:28:23 +0630 Subject: [PATCH 01/13] feat: add dynamic og image using satori --- package-lock.json | 183 ++++++++++++++++++++++++++++++++++ package.json | 1 + src/pages/[ogTitle].svg.ts | 18 ++++ src/utils/generateOgImage.tsx | 39 ++++++++ 4 files changed, 241 insertions(+) create mode 100644 src/pages/[ogTitle].svg.ts create mode 100644 src/utils/generateOgImage.tsx diff --git a/package-lock.json b/package-lock.json index 13c060cf0..8da257cd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "github-slugger": "^2.0.0", "remark-collapse": "^0.1.2", "remark-toc": "^8.0.1", + "satori": "^0.0.44", "tailwindcss": "^3.2.4" }, "devDependencies": { @@ -960,6 +961,21 @@ "@proload/core": "^0.3.2" } }, + "node_modules/@shuding/opentype.js": { + "version": "1.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", + "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==", + "dependencies": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/@tailwindcss/typography": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz", @@ -1189,6 +1205,11 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, + "node_modules/@types/yoga-layout": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", + "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==" + }, "node_modules/@vscode/emmet-helper": { "version": "2.8.4", "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.8.4.tgz", @@ -1832,6 +1853,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001426", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001426.tgz", @@ -2168,6 +2197,34 @@ "node": ">= 8" } }, + "node_modules/css-background-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz", + "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==" + }, + "node_modules/css-box-shadow": { + "version": "1.0.0-3", + "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", + "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==" + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2946,6 +3003,11 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==" + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -7142,6 +7204,28 @@ "suf-log": "^2.5.3" } }, + "node_modules/satori": { + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.0.44.tgz", + "integrity": "sha512-WKUxXC2qeyno6J3ucwwLozPL6j1HXOZiN5wIUf7iqAhlx1RUC/6ePIKHi7iPc3Cy6DYuZcJriZXxXkSdo2FQHg==", + "dependencies": { + "@shuding/opentype.js": "1.4.0-beta.0", + "css-background-parser": "^0.1.0", + "css-box-shadow": "1.0.0-3", + "css-to-react-native": "^3.0.0", + "emoji-regex": "^10.2.1", + "postcss-value-parser": "^4.2.0", + "yoga-layout-prebuilt": "^1.10.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/satori/node_modules/emoji-regex": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", + "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==" + }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -7396,6 +7480,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "node_modules/stringify-entities": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", @@ -8727,6 +8816,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoga-layout-prebuilt": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz", + "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==", + "dependencies": { + "@types/yoga-layout": "1.9.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/zod": { "version": "3.19.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.19.1.tgz", @@ -9459,6 +9559,15 @@ "tsm": "^2.1.4" } }, + "@shuding/opentype.js": { + "version": "1.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", + "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==", + "requires": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + } + }, "@tailwindcss/typography": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz", @@ -9685,6 +9794,11 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, + "@types/yoga-layout": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", + "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==" + }, "@vscode/emmet-helper": { "version": "2.8.4", "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.8.4.tgz", @@ -10139,6 +10253,11 @@ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-lite": { "version": "1.0.30001426", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001426.tgz", @@ -10368,6 +10487,31 @@ "which": "^2.0.1" } }, + "css-background-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz", + "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==" + }, + "css-box-shadow": { + "version": "1.0.0-3", + "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", + "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==" + }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -10829,6 +10973,11 @@ "web-streams-polyfill": "^3.0.3" } }, + "fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==" + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -13668,6 +13817,27 @@ "suf-log": "^2.5.3" } }, + "satori": { + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.0.44.tgz", + "integrity": "sha512-WKUxXC2qeyno6J3ucwwLozPL6j1HXOZiN5wIUf7iqAhlx1RUC/6ePIKHi7iPc3Cy6DYuZcJriZXxXkSdo2FQHg==", + "requires": { + "@shuding/opentype.js": "1.4.0-beta.0", + "css-background-parser": "^0.1.0", + "css-box-shadow": "1.0.0-3", + "css-to-react-native": "^3.0.0", + "emoji-regex": "^10.2.1", + "postcss-value-parser": "^4.2.0", + "yoga-layout-prebuilt": "^1.10.0" + }, + "dependencies": { + "emoji-regex": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", + "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==" + } + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -13859,6 +14029,11 @@ "strip-ansi": "^7.0.1" } }, + "string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "stringify-entities": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", @@ -14672,6 +14847,14 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, + "yoga-layout-prebuilt": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz", + "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==", + "requires": { + "@types/yoga-layout": "1.9.2" + } + }, "zod": { "version": "3.19.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.19.1.tgz", diff --git a/package.json b/package.json index 4b9ad1fe0..faf831357 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "github-slugger": "^2.0.0", "remark-collapse": "^0.1.2", "remark-toc": "^8.0.1", + "satori": "^0.0.44", "tailwindcss": "^3.2.4" }, "devDependencies": { diff --git a/src/pages/[ogTitle].svg.ts b/src/pages/[ogTitle].svg.ts new file mode 100644 index 000000000..72446875f --- /dev/null +++ b/src/pages/[ogTitle].svg.ts @@ -0,0 +1,18 @@ +import type { APIRoute } from "astro"; +import generateOgImage from "@utils/generateOgImage"; + +export const get: APIRoute = async ({ params, request }) => { + const ogTitle = params.ogTitle ?? "Hello World"; + console.log(ogTitle); + return { + body: await generateOgImage(ogTitle), + }; +}; + +export function getStaticPaths() { + return [ + { params: { ogTitle: "Post 1" } }, + { params: { ogTitle: "Post 2" } }, + { params: { ogTitle: "Post 3" } }, + ]; +} diff --git a/src/utils/generateOgImage.tsx b/src/utils/generateOgImage.tsx new file mode 100644 index 000000000..04427adf0 --- /dev/null +++ b/src/utils/generateOgImage.tsx @@ -0,0 +1,39 @@ +import satori, { SatoriOptions } from "satori"; +import { SITE } from "@config"; + +const fetchFont = async () => { + const fontFile = await fetch( + "https://www.1001fonts.com/download/font/ibm-plex-mono.regular.ttf" + ); + const fontData: ArrayBuffer = await fontFile.arrayBuffer(); + return fontData; +}; + +const fontData = await fetchFont(); + +const ogImage = (text = "hello") => { + return ( +
+ {text} +

{SITE.author}

+
+ ); +}; + +const options: SatoriOptions = { + width: 600, + height: 400, + fonts: [ + { + name: "IBM Plex Mono", + data: fontData, + weight: 400, + style: "normal", + }, + ], +}; + +const generateOgImage = async (mytext: string) => + await satori(ogImage(mytext), options); + +export default generateOgImage; From 8a3c14d3390b20107ebfb4385f29e30fbc773cb9 Mon Sep 17 00:00:00 2001 From: satnaing Date: Sun, 25 Dec 2022 00:50:05 +0630 Subject: [PATCH 02/13] feat: add og card styling --- src/utils/generateOgImage.tsx | 116 ++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 13 deletions(-) diff --git a/src/utils/generateOgImage.tsx b/src/utils/generateOgImage.tsx index 04427adf0..651382fd7 100644 --- a/src/utils/generateOgImage.tsx +++ b/src/utils/generateOgImage.tsx @@ -1,39 +1,129 @@ import satori, { SatoriOptions } from "satori"; import { SITE } from "@config"; -const fetchFont = async () => { - const fontFile = await fetch( +const fetchFonts = async () => { + // Regular Font + const fontFileRegular = await fetch( "https://www.1001fonts.com/download/font/ibm-plex-mono.regular.ttf" ); - const fontData: ArrayBuffer = await fontFile.arrayBuffer(); - return fontData; + const fontRegular: ArrayBuffer = await fontFileRegular.arrayBuffer(); + + // Bold Font + const fontFileBold = await fetch( + "https://www.1001fonts.com/download/font/ibm-plex-mono.bold.ttf" + ); + const fontBold: ArrayBuffer = await fontFileBold.arrayBuffer(); + + return { fontRegular, fontBold }; }; -const fontData = await fetchFont(); +const { fontRegular, fontBold } = await fetchFonts(); -const ogImage = (text = "hello") => { +const ogImage = (text: string) => { return ( -
- {text} -

{SITE.author}

+
+
+
+

+ {text} +

+
+ + by{" "} + + " + + + {SITE.author} + + + + + {SITE.title} + +
+
+
); }; const options: SatoriOptions = { - width: 600, - height: 400, + width: 1200, + height: 630, + // debug: true, fonts: [ { name: "IBM Plex Mono", - data: fontData, + data: fontRegular, weight: 400, style: "normal", }, + { + name: "IBM Plex Mono", + data: fontBold, + weight: 600, + style: "normal", + }, ], }; -const generateOgImage = async (mytext: string) => +const generateOgImage = async (mytext = SITE.title) => await satori(ogImage(mytext), options); export default generateOgImage; From dd498e538b09c992979c192702f8f09ac98666ee Mon Sep 17 00:00:00 2001 From: satnaing Date: Sun, 25 Dec 2022 00:50:40 +0630 Subject: [PATCH 03/13] feat: add dynamic paths for ogTitle --- src/pages/[ogTitle].svg.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/pages/[ogTitle].svg.ts b/src/pages/[ogTitle].svg.ts index 72446875f..3bcbc2943 100644 --- a/src/pages/[ogTitle].svg.ts +++ b/src/pages/[ogTitle].svg.ts @@ -1,18 +1,23 @@ -import type { APIRoute } from "astro"; +import type { APIRoute, MarkdownInstance } from "astro"; import generateOgImage from "@utils/generateOgImage"; +import type { Frontmatter } from "@types"; -export const get: APIRoute = async ({ params, request }) => { - const ogTitle = params.ogTitle ?? "Hello World"; - console.log(ogTitle); +export const get: APIRoute = async ({ params }) => { return { - body: await generateOgImage(ogTitle), + body: await generateOgImage(params.ogTitle), }; }; +const postImportResult = import.meta.glob>( + "../contents/**/**/*.md", + { + eager: true, + } +); +const posts = Object.values(postImportResult); + export function getStaticPaths() { - return [ - { params: { ogTitle: "Post 1" } }, - { params: { ogTitle: "Post 2" } }, - { params: { ogTitle: "Post 3" } }, - ]; + return posts.map(post => ({ + params: { ogTitle: post.frontmatter.title }, + })); } From c553bc59cde846474cd6a72b1c98bc3d73d72282 Mon Sep 17 00:00:00 2001 From: satnaing Date: Sun, 25 Dec 2022 00:51:02 +0630 Subject: [PATCH 04/13] feat: add default og url --- src/layouts/PostDetails.astro | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/layouts/PostDetails.astro b/src/layouts/PostDetails.astro index bd0ab533e..f10b9ab9e 100644 --- a/src/layouts/PostDetails.astro +++ b/src/layouts/PostDetails.astro @@ -14,14 +14,12 @@ export interface Props { const { frontmatter, Content } = Astro.props.post; const { title, author, description, ogImage, datetime, tags } = frontmatter; + +const ogUrl = new URL(ogImage ? ogImage : `${title}.svg`, Astro.url.origin) + .href; --- - +