Skip to content

Commit

Permalink
kickstart dynamic og
Browse files Browse the repository at this point in the history
  • Loading branch information
vasfvitor committed Oct 11, 2023
1 parent 64ecef4 commit c044e22
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 10 deletions.
11 changes: 1 addition & 10 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,8 @@ export default defineConfig({
MarkdownContent: 'starlight-blog/overrides/MarkdownContent.astro',
Sidebar: 'starlight-blog/overrides/Sidebar.astro',
ThemeSelect: 'starlight-blog/overrides/ThemeSelect.astro',
Head: '@components/overrides/Head.astro',
},
head: [
{
tag: 'meta',
attrs: { property: 'og:image', content: site + '/og.png?v=1' },
},
{
tag: 'meta',
attrs: { property: 'twitter:image', content: site + '/og.png?v=1' },
},
],
// TODO: Be sure this is updated when the branch is switched
editLink: {
baseUrl: 'https://github.com/tauri-apps/tauri-docs/edit/next',
Expand Down
18 changes: 18 additions & 0 deletions src/components/overrides/Head.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
import type { Props } from '@astrojs/starlight/props';
import Default from '@astrojs/starlight/components/Head.astro';
const socialImage = new URL(
`/social-images/content/docs${
Astro.url.pathname == '/' ? '/index' : Astro.url.pathname.replace(/\/$/, '')
}.png`,
Astro.url
);
---

<!-- /* TODO: - Check if is possible dynamic generate on astro.config head without overriding Head.astro
- Make sure the URL pattern matches every page and if default fallback is working.
-->
<Default {...Astro.props}></Default>
<meta name="twitter:image" content={socialImage} />
<meta property="og:image" content={socialImage} />
134 changes: 134 additions & 0 deletions src/pages/social-images/[...path].png.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// ISSUE: Some pages don't have description field in the frontmatter, but have title. In those cases it is returning undefined written on the OG image. Example: /features/commands/

// TODO: Define default or import from somewhere else
const SITE_TITLE = 'Tauri';
const SITE_DESCRIPTION = 'Tauri is awesome';

import { createRequire } from 'module';

// We can't import sharp normally because it's a CJS thing and those don't seems to work well with Astro, Vite, everyone
const cjs = createRequire(import.meta.url);
const sharp = cjs('sharp');

const blogPosts = Object.fromEntries(
Object.entries(import.meta.glob('../../content/docs/**/*.{md,mdx}')).map(([path, getInfo]) => {
path = path.replaceAll('../', '');
path = path.replace('.mdx', '');
path = path.replace('.md', '');

return [path, getInfo];
})
);

export async function getStaticPaths() {
const paths = ['index'];

for (const [path, getInfo] of Object.entries(blogPosts)) {
const info = (await getInfo()) as Record<string, any>;

if (!info.frontmatter.draft) {
paths.push(path);
}
}

return paths.map((path) => ({ params: { path } }));
}

export async function GET({ params, request }) {
const getInfo = blogPosts[params.path];

let template;
if (getInfo) {
const info = (await getInfo()) as Record<string, any>;
template = createTemplate(info.frontmatter.title, info.frontmatter.description);
} else {
template = createTemplate(SITE_TITLE, SITE_DESCRIPTION);
}

// Generate our image
const svgBuffer = Buffer.from(template);
const body = await sharp(svgBuffer).resize(1200, 675).png().toBuffer();

return new Response(body);
}

// breaks a string into a set of strings with a maximum length.
// This is used to prevent horizontal text overflow
function breakText(str: string, maxLines: number, maxLineLen: number) {
const segmenterTitle = new Intl.Segmenter('en-US', { granularity: 'word' });
const segments = segmenterTitle.segment(str);

let linesOut = [''];
let lineNo = 0;
let offsetInLine = 0;
for (const word of Array.from(segments)) {
if (offsetInLine + word.segment.length >= maxLineLen) {
lineNo++;
offsetInLine = 0;
linesOut.push('');
}

if (lineNo >= maxLines) {
return linesOut.slice(0, maxLines);
}

linesOut[lineNo] += word.segment;
offsetInLine += word.segment.length;
}
return linesOut;
}

function createTemplate(title: string, description: string): string {
const [titleFirstLine, titleSecondLine] = breakText(title, 2, 30);

const [descriptionFirstLine, descriptionSecondLine, descriptionThirdLine] = breakText(
description,
3,
40
);
// TODO: Make a proper design and revise text layout.
return `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1200 675">
<style>
.title {
fill: #fff;
font: bold 64pt sans-serif;
}
.description {
fill: rgb(132, 131, 130);
font: 40pt sans-serif;
}
.link {
fill: rgb(50, 50, 50);
font: 32pt sans-serif;
}
</style>
<text x="50" y="350" font-family="Open Sans" class="title">${titleFirstLine || ''}</text>
<text x="50" y="420" font-family="Open Sans" class="title">${titleSecondLine || ''}</text>
<!-- max length 25 chars -->
<text x="50" y="490" font-family="Open Sans" class="description"
>${descriptionFirstLine || ''}</text
>
<text x="50" y="530" font-family="Open Sans" class="description"
>${descriptionSecondLine || ''}</text
>
<text x="50" y="570" font-family="Open Sans" class="description"
>${descriptionThirdLine || ''}</text
>
<text x="840" y="620" font-family="Open Sans" class="link">TAURI IS AWESOME</text>
<defs>
<radialGradient
id="a"
cx="0"
cy="0"
r="1"
gradientTransform="matrix(0 778 -1531 0 1126 629)"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#fff"></stop>
<stop offset="1" stop-color="#fff" stop-opacity="0"></stop>
</radialGradient>
</defs>
</svg>`;
}

0 comments on commit c044e22

Please sign in to comment.