-
-
Notifications
You must be signed in to change notification settings - Fork 807
Add share cards to website-next #9804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { ImageResponse } from "next/og"; | ||
| import { loadInterFonts } from "@/src/og/fonts"; | ||
| import { ShareCard } from "@/src/og/ShareCard"; | ||
|
|
||
| // TODO: Proper styling and layout of share cards | ||
|
|
||
| // Required under `output: export` for this paramless metadata route, which has | ||
| // no `generateStaticParams` to imply prerendering (unlike the per-doc card). | ||
| export const dynamic = "force-static"; | ||
|
|
||
| // Mirrors the brand strings in app/layout.tsx (TITLE is the headline). | ||
| const TITLE = "ChilliCream GraphQL Platform"; | ||
|
|
||
| export const alt = TITLE; | ||
|
|
||
| export const size = { | ||
| width: 1200, | ||
| height: 630, | ||
| }; | ||
|
|
||
| export const contentType = "image/png"; | ||
|
|
||
| export default async function Image() { | ||
| const fonts = await loadInterFonts(); | ||
|
|
||
| return new ImageResponse( | ||
| <ShareCard badge="ChilliCream" eyebrow="chillicream.com" title={TITLE} />, | ||
| { | ||
| ...size, | ||
| fonts, | ||
| }, | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import path from "node:path"; | ||
| import { ImageResponse } from "next/og"; | ||
| import { PRODUCTS } from "@/src/data/products"; | ||
| import { | ||
| CONTENT_ROOT, | ||
| decodeDocId, | ||
| encodeDocId, | ||
| listDocSlugs, | ||
| resolveFile, | ||
| } from "@/src/helpers/docsParams"; | ||
| import { readFrontmatter } from "@/src/helpers/readFrontmatter"; | ||
| import { loadInterFonts } from "@/src/og/fonts"; | ||
| import { ShareCard } from "@/src/og/ShareCard"; | ||
|
|
||
| // TODO: Proper styling and layout of share cards | ||
|
|
||
| export const dynamicParams = false; | ||
|
|
||
| export const alt = "ChilliCream documentation"; | ||
|
|
||
| export const size = { | ||
| width: 1200, | ||
| height: 630, | ||
| }; | ||
|
|
||
| export const contentType = "image/png"; | ||
|
|
||
| type Params = { | ||
| id: string; | ||
| }; | ||
|
|
||
| export function generateStaticParams(): Params[] { | ||
| return listDocSlugs().map((slug) => ({ id: encodeDocId(slug) })); | ||
| } | ||
|
|
||
| export default async function Image({ params }: { params: Promise<Params> }) { | ||
| const { id } = await params; | ||
| const slug = decodeDocId(id); | ||
| const rel = resolveFile(slug); | ||
| const frontmatter = rel | ||
| ? readFrontmatter(path.join(CONTENT_ROOT, rel)) | ||
| : null; | ||
|
|
||
| const productSlug = slug[0]; | ||
| const product = PRODUCTS.find((p) => p.slug === productSlug); | ||
| const eyebrow = product?.title ?? "ChilliCream"; | ||
| const title = frontmatter?.title ?? product?.title ?? "ChilliCream"; | ||
|
|
||
| const fonts = await loadInterFonts(); | ||
|
|
||
| return new ImageResponse( | ||
| <ShareCard badge={eyebrow} eyebrow={eyebrow} title={title} />, | ||
| { | ||
| ...size, | ||
| fonts, | ||
| }, | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import fs from "node:fs"; | ||
| import path from "node:path"; | ||
|
|
||
| /** | ||
| * Root directory that holds all docs markdown content. Shared by the docs page | ||
| * and its Open Graph image route so they enumerate the exact same slugs. | ||
| */ | ||
| export const CONTENT_ROOT = path.join(process.cwd(), "content/docs"); | ||
|
|
||
| /** | ||
| * Enumerates the doc slugs (one `string[]` per route) from the markdown files | ||
| * under `CONTENT_ROOT`. `index.md(x)` files map to their parent directory. | ||
| * | ||
| * `output: export` requires at least one prerendered path, so a `__empty__` | ||
| * placeholder is returned when no content is present; the page renders a 404 | ||
| * for it via `notFound()`. | ||
| */ | ||
| export function listDocSlugs(): string[][] { | ||
| const slugs = walk(CONTENT_ROOT) | ||
| .filter((f) => /\.mdx?$/.test(f)) | ||
| .map((f) => path.relative(CONTENT_ROOT, f).replace(/\.mdx?$/, "")) | ||
| .map((rel) => rel.split(path.sep)) | ||
| .map((parts) => | ||
| parts[parts.length - 1] === "index" ? parts.slice(0, -1) : parts, | ||
| ) | ||
| .filter((slug) => slug.length > 0); | ||
|
|
||
| return slugs.length > 0 ? slugs : [["__empty__"]]; | ||
| } | ||
|
|
||
| /** | ||
| * Separator used to flatten a doc slug (`["foo", "bar"]`) into the single | ||
| * opaque `[id]` segment of the Open Graph image route. Catch-all segments | ||
| * cannot be followed by the `opengraph-image` file convention, so the docs | ||
| * share-card route lives under `app/docs-og/[id]` and the page metadata points | ||
| * at it. No doc slug contains this separator. | ||
| */ | ||
| const SLUG_ID_SEPARATOR = "__"; | ||
|
|
||
| /** Flattens a doc slug into the opaque `[id]` segment. */ | ||
| export function encodeDocId(slug: string[]): string { | ||
| return slug.join(SLUG_ID_SEPARATOR); | ||
| } | ||
|
|
||
| /** Expands an opaque `[id]` segment back into a doc slug. */ | ||
| export function decodeDocId(id: string): string[] { | ||
| return id.split(SLUG_ID_SEPARATOR); | ||
| } | ||
|
|
||
| /** | ||
| * Resolves a doc slug to its markdown file path relative to `CONTENT_ROOT`, | ||
| * or `null` when no matching file exists. | ||
| */ | ||
| export function resolveFile(slug: string[]): string | null { | ||
| const joined = slug.join("/"); | ||
| const candidates = [ | ||
| `${joined}.md`, | ||
| `${joined}.mdx`, | ||
| `${joined}/index.md`, | ||
| `${joined}/index.mdx`, | ||
| ]; | ||
|
|
||
| for (const c of candidates) { | ||
| if (fs.existsSync(path.join(CONTENT_ROOT, c))) { | ||
| return c; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| function walk(dir: string): string[] { | ||
| const entries = fs.readdirSync(dir, { withFileTypes: true }); | ||
| return entries.flatMap((e) => { | ||
| const full = path.join(dir, e.name); | ||
| return e.isDirectory() ? walk(full) : [full]; | ||
| }); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.