From b52e0f19388ba9c6ec42d3ce2f133bb062efffeb Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Wed, 26 Feb 2025 15:48:27 +0000 Subject: [PATCH 01/10] [Docs Site] Add cover property to frontmatter for custom images --- src/components/overrides/Head.astro | 157 ++++++++--------------- src/content/docs/workers/index.mdx | 1 + src/schemas/base.ts | 186 ++++++++++++++-------------- src/util/props.ts | 55 ++++++++ 4 files changed, 202 insertions(+), 197 deletions(-) diff --git a/src/components/overrides/Head.astro b/src/components/overrides/Head.astro index 0021e2de4a9e887..a70eff0eb8bcc11 100644 --- a/src/components/overrides/Head.astro +++ b/src/components/overrides/Head.astro @@ -5,40 +5,40 @@ import { differenceInCalendarDays } from "date-fns"; import "tippy.js/dist/tippy.css"; import { getEntry } from "astro:content"; +import { getOgImage } from "~/util/props"; // grab the current top-level folder. Remove . characters for 1.1.1.1 URL const currentSection = Astro.url.pathname.split("/")[1].replaceAll(".", ""); +const head = Astro.props.entry.data.head; if (currentSection) { const product = await getEntry("products", currentSection); if (product) { if (product.data.meta.title) { - const titleIdx = Astro.props.entry.data.head.findIndex( - (x) => x.tag === "title", - ); + const titleIdx = head.findIndex((x) => x.tag === "title"); let title: string; if (titleIdx !== -1) { - const existingTitle = Astro.props.entry.data.head[titleIdx].content; + const existingTitle = head[titleIdx].content; title = `${existingTitle} · ${product.data.meta.title}`; - Astro.props.entry.data.head[titleIdx] = { + head[titleIdx] = { tag: "title", attrs: {}, content: title, }; } else { title = `${Astro.props.entry.data.title} · ${product.data.meta.title}`; - Astro.props.entry.data.head.push({ + head.push({ tag: "title", attrs: {}, content: title, }); } - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { property: "og:title", content: title }, content: "", @@ -47,7 +47,7 @@ if (currentSection) { if (product.data.product.title) { const productName = product.data.product.title; - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "pcx_product", @@ -55,7 +55,7 @@ if (currentSection) { }, content: "", }); - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "algolia_product_filter", @@ -66,58 +66,7 @@ if (currentSection) { } if (product.data.product.group) { - const group = product.data.product.group.toLowerCase(); - - let ogImage = "https://developers.cloudflare.com/cf-twitter-card.png"; - - const images: Record = { - "cloudflare essentials": - "https://developers.cloudflare.com/core-services-preview.png", - "cloudflare one": "https://developers.cloudflare.com/zt-preview.png", - "developer platform": - "https://developers.cloudflare.com/dev-products-preview.png", - "network security": - "https://developers.cloudflare.com/core-services-preview.png", - "application performance": - "https://developers.cloudflare.com/core-services-preview.png", - "application security": - "https://developers.cloudflare.com/core-services-preview.png", - }; - - if (images[group]) { - ogImage = images[group]; - } - - const tags = [ - { - tag: "meta", - attrs: { - name: "image", - content: ogImage, - }, - content: "", - }, - { - tag: "meta", - attrs: { - name: "og:image", - content: ogImage, - }, - content: "", - }, - { - tag: "meta", - attrs: { - name: "twitter:image", - content: ogImage, - }, - content: "", - }, - ] as const; - - Astro.props.entry.data.head.push(...tags); - - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "pcx_content_group", @@ -129,7 +78,7 @@ if (currentSection) { } if (currentSection === "style-guide") { - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "robots", @@ -138,46 +87,12 @@ if (currentSection) { content: "", }); } - - if (currentSection === "changelog") { - let changelogImage = - "https://developers.cloudflare.com/changelog-preview.png"; - - const tags = [ - { - tag: "meta", - attrs: { - name: "image", - content: changelogImage, - }, - content: "", - }, - { - tag: "meta", - attrs: { - name: "og:image", - content: changelogImage, - }, - content: "", - }, - { - tag: "meta", - attrs: { - name: "twitter:image", - content: changelogImage, - }, - content: "", - }, - ] as const; - - Astro.props.entry.data.head.push(...tags); - } } // Add noindex tag if present in frontmatter if (Astro.props.entry.data.noindex) { - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "robots", @@ -191,7 +106,7 @@ if (Astro.props.entry.data.noindex) { // content type if (Astro.props.entry.data.pcx_content_type) { const contentType = Astro.props.entry.data.pcx_content_type; - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "pcx_content_type", @@ -199,7 +114,7 @@ if (Astro.props.entry.data.pcx_content_type) { }, content: "", }); - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "algolia_content_type", @@ -212,7 +127,7 @@ if (Astro.props.entry.data.pcx_content_type) { // other products if (Astro.props.entry.data.products) { const additionalProducts = Astro.props.entry.data.products; - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "pcx_additional_products", @@ -225,7 +140,7 @@ if (Astro.props.entry.data.products) { // other products if (Astro.props.entry.data.tags) { const pageTags = Astro.props.entry.data.tags; - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "pcx_tags", @@ -240,7 +155,7 @@ if (Astro.props.entry.data.updated) { new Date(), Astro.props.entry.data.updated, ); - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { name: "pcx_last_reviewed", @@ -256,7 +171,7 @@ if (Astro.props.entry.data.pcx_content_type === "changelog") { const href = new URL(Astro.site ?? ""); href.pathname = Astro.props.entry.slug.concat("/index.xml"); - Astro.props.entry.data.head.push({ + head.push({ tag: "link", attrs: { rel: "alternate", @@ -268,7 +183,7 @@ if (Astro.props.entry.data.pcx_content_type === "changelog") { } if (Astro.props.entry.data.external_link) { - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { content: "noindex", @@ -276,7 +191,7 @@ if (Astro.props.entry.data.external_link) { }, content: "", }); - Astro.props.entry.data.head.push({ + head.push({ tag: "meta", attrs: { content: `0; url=${Astro.props.entry.data.external_link}`, @@ -285,6 +200,38 @@ if (Astro.props.entry.data.external_link) { content: "", }); } + +const ogImagePath = await getOgImage(Astro.props.entry); +const ogImageUrl = new URL(ogImagePath, Astro.site!.origin).toString(); + +const ogImageTags = [ + { + tag: "meta", + attrs: { + name: "image", + content: ogImageUrl, + }, + content: "", + }, + { + tag: "meta", + attrs: { + name: "og:image", + content: ogImageUrl, + }, + content: "", + }, + { + tag: "meta", + attrs: { + name: "twitter:image", + content: ogImageUrl, + }, + content: "", + }, +] as const; + +head.push(...ogImageTags); --- diff --git a/src/content/docs/workers/index.mdx b/src/content/docs/workers/index.mdx index c6f9540bbcb2473..adc5a03937a0bf0 100644 --- a/src/content/docs/workers/index.mdx +++ b/src/content/docs/workers/index.mdx @@ -7,6 +7,7 @@ sidebar: head: - tag: title content: Cloudflare Workers +cover: ~/assets/images/workers/kv-write.svg --- import { diff --git a/src/schemas/base.ts b/src/schemas/base.ts index 3a31a6ce3829d11..87ae7ba31a19ab2 100644 --- a/src/schemas/base.ts +++ b/src/schemas/base.ts @@ -12,95 +12,97 @@ const spotlightAuthorDetails = z "These are used to automatically add the SpotlightAuthorDetails component to the page. Refer to https://developers.cloudflare.com/style-guide/components/spotlight-author-details/.", ); -export const baseSchema = z.object({ - pcx_content_type: z - .union([ - z.literal("overview"), - z.literal("get-started"), - z.literal("how-to"), - z.literal("concept"), - z.literal("reference"), - z.literal("reference-architecture"), - z.literal("reference-architecture-diagram"), - z.literal("tutorial"), - z.literal("api"), - z.literal("troubleshooting"), - z.literal("faq"), - z.literal("integration-guide"), - z.literal("changelog"), - z.literal("configuration"), - z.literal("navigation"), - z.literal("example"), - z.literal("learning-unit"), - z.literal("design-guide"), - z.literal("video"), - ]) - .catch((ctx) => ctx.input) - .optional() - .describe( - "Refer to https://developers.cloudflare.com/style-guide/documentation-content-strategy/content-types/.", - ), - content_type: z.string().optional(), - tags: z.string().array().optional(), - external_link: z - .string() - .optional() - .describe( - "Links to this page (i.e sidebar, directory listing) will instead appear as the provided link.", - ), - difficulty: z - .union([ - z.literal("Beginner"), - z.literal("Intermediate"), - z.literal("Advanced"), - ]) - .catch((ctx) => ctx.input) - .optional() - .describe( - "Difficulty is displayed as a column in the ListTutorials component.", - ), - updated: z - .date() - .optional() - .describe( - "This is used to automatically add the LastReviewed component to a page. Refer to https://developers.cloudflare.com/style-guide/components/last-reviewed/.", - ), - spotlight: spotlightAuthorDetails, - release_notes_file_name: z.string().array().optional(), - release_notes_product_area_name: z.string().optional(), - products: z.string().array().optional(), - languages: z.string().array().optional(), - summary: z.string().optional(), - goal: z.string().array().optional(), - operation: z.string().array().optional(), - noindex: z.boolean().optional(), - sidebar: z - .object({ - order: z.number().optional(), - label: z.string().optional(), - group: z - .object({ - label: z - .string() - .optional() - .describe( - "Overrides the default 'Overview' label for index pages in the sidebar. Refer to https://developers.cloudflare.com/style-guide/frontmatter/sidebar/.", - ), - hideIndex: z - .boolean() - .default(false) - .describe( - "Hides the index page from the sidebar. Refer to https://developers.cloudflare.com/style-guide/frontmatter/sidebar/.", - ), - badge: BadgeConfigSchema(), - }) - .optional(), - }) - .optional(), - hideChildren: z - .boolean() - .optional() - .describe( - "Renders this group as a single link on the sidebar, to the index page. Refer to https://developers.cloudflare.com/style-guide/frontmatter/sidebar/.", - ), -}); +export const baseSchema = ({ image }) => + z.object({ + cover: image().optional(), + pcx_content_type: z + .union([ + z.literal("overview"), + z.literal("get-started"), + z.literal("how-to"), + z.literal("concept"), + z.literal("reference"), + z.literal("reference-architecture"), + z.literal("reference-architecture-diagram"), + z.literal("tutorial"), + z.literal("api"), + z.literal("troubleshooting"), + z.literal("faq"), + z.literal("integration-guide"), + z.literal("changelog"), + z.literal("configuration"), + z.literal("navigation"), + z.literal("example"), + z.literal("learning-unit"), + z.literal("design-guide"), + z.literal("video"), + ]) + .catch((ctx) => ctx.input) + .optional() + .describe( + "Refer to https://developers.cloudflare.com/style-guide/documentation-content-strategy/content-types/.", + ), + content_type: z.string().optional(), + tags: z.string().array().optional(), + external_link: z + .string() + .optional() + .describe( + "Links to this page (i.e sidebar, directory listing) will instead appear as the provided link.", + ), + difficulty: z + .union([ + z.literal("Beginner"), + z.literal("Intermediate"), + z.literal("Advanced"), + ]) + .catch((ctx) => ctx.input) + .optional() + .describe( + "Difficulty is displayed as a column in the ListTutorials component.", + ), + updated: z + .date() + .optional() + .describe( + "This is used to automatically add the LastReviewed component to a page. Refer to https://developers.cloudflare.com/style-guide/components/last-reviewed/.", + ), + spotlight: spotlightAuthorDetails, + release_notes_file_name: z.string().array().optional(), + release_notes_product_area_name: z.string().optional(), + products: z.string().array().optional(), + languages: z.string().array().optional(), + summary: z.string().optional(), + goal: z.string().array().optional(), + operation: z.string().array().optional(), + noindex: z.boolean().optional(), + sidebar: z + .object({ + order: z.number().optional(), + label: z.string().optional(), + group: z + .object({ + label: z + .string() + .optional() + .describe( + "Overrides the default 'Overview' label for index pages in the sidebar. Refer to https://developers.cloudflare.com/style-guide/frontmatter/sidebar/.", + ), + hideIndex: z + .boolean() + .default(false) + .describe( + "Hides the index page from the sidebar. Refer to https://developers.cloudflare.com/style-guide/frontmatter/sidebar/.", + ), + badge: BadgeConfigSchema(), + }) + .optional(), + }) + .optional(), + hideChildren: z + .boolean() + .optional() + .describe( + "Renders this group as a single link on the sidebar, to the index page. Refer to https://developers.cloudflare.com/style-guide/frontmatter/sidebar/.", + ), + }); diff --git a/src/util/props.ts b/src/util/props.ts index c19e5a511fd6622..51e6c66edefc0f4 100644 --- a/src/util/props.ts +++ b/src/util/props.ts @@ -4,6 +4,8 @@ import he from "he"; import { remark } from "remark"; import strip from "strip-markdown"; import { rehypeExternalLinksOptions } from "~/plugins/rehype/external-links"; +import { getImage } from "astro:assets"; +import { getEntry, type CollectionEntry } from "astro:content"; type TableOfContentsItems = NonNullable["items"]; @@ -88,3 +90,56 @@ export async function generateDescription({ ?.replaceAll(rehypeExternalLinksOptions.content.value, "") .trim(); } + +const DEFAULT_OG_IMAGE = "/cf-twitter-card.png"; + +const CHANGELOG_OG_IMAGE = "/changelog-preview.png"; + +const PRODUCT_AREA_OG_IMAGES: Record = { + "cloudflare essentials": "/core-services-preview.png", + "cloudflare one": "/zt-preview.png", + "developer platform": "/dev-products-preview.png", + "network security": "/core-services-preview.png", + "application performance": "/core-services-preview.png", + "application security": "/core-services-preview.png", +}; + +export async function getOgImage(entry: CollectionEntry<"docs">) { + if (entry.data.cover) { + if (!entry.data.cover.src) { + throw new Error( + `${entry.id} has a cover property in frontmatter that is not a valid image path`, + ); + } + + const image = await getImage({ + src: entry.data.cover, + format: "png", + }); + + return image.src; + } + + const section = entry.id.split("/").filter(Boolean).at(0); + + if (!section) { + return DEFAULT_OG_IMAGE; + } + + if (section === "changelog") { + return CHANGELOG_OG_IMAGE; + } + + const product = await getEntry("products", section); + + if (product && product.data.product.group) { + const image = + PRODUCT_AREA_OG_IMAGES[product.data.product.group.toLowerCase()]; + + if (image) { + return image; + } + } + + return DEFAULT_OG_IMAGE; +} From 485420eccc9aee3685ddd90d558237d7d24ae77c Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Wed, 26 Feb 2025 16:08:33 +0000 Subject: [PATCH 02/10] Use Astro.url for base, add to changelog schema --- src/components/overrides/Head.astro | 2 +- src/schemas/base.ts | 3 ++- src/schemas/changelog.ts | 26 ++++++++++++++------------ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/components/overrides/Head.astro b/src/components/overrides/Head.astro index a70eff0eb8bcc11..645a064f5ac323b 100644 --- a/src/components/overrides/Head.astro +++ b/src/components/overrides/Head.astro @@ -202,7 +202,7 @@ if (Astro.props.entry.data.external_link) { } const ogImagePath = await getOgImage(Astro.props.entry); -const ogImageUrl = new URL(ogImagePath, Astro.site!.origin).toString(); +const ogImageUrl = new URL(ogImagePath, Astro.url.origin).toString(); const ogImageTags = [ { diff --git a/src/schemas/base.ts b/src/schemas/base.ts index 87ae7ba31a19ab2..1bf6892d96fa040 100644 --- a/src/schemas/base.ts +++ b/src/schemas/base.ts @@ -1,5 +1,6 @@ import { z } from "astro:schema"; import { BadgeConfigSchema } from "./types/badge"; +import type { SchemaContext } from "astro:content"; const spotlightAuthorDetails = z .object({ @@ -12,7 +13,7 @@ const spotlightAuthorDetails = z "These are used to automatically add the SpotlightAuthorDetails component to the page. Refer to https://developers.cloudflare.com/style-guide/components/spotlight-author-details/.", ); -export const baseSchema = ({ image }) => +export const baseSchema = ({ image }: SchemaContext) => z.object({ cover: image().optional(), pcx_content_type: z diff --git a/src/schemas/changelog.ts b/src/schemas/changelog.ts index 7c705e715b09bc2..ab2ac7c320dc5cf 100644 --- a/src/schemas/changelog.ts +++ b/src/schemas/changelog.ts @@ -1,14 +1,16 @@ -import { reference } from "astro:content"; +import { reference, type SchemaContext } from "astro:content"; import { z } from "astro:schema"; -export const changelogSchema = z.object({ - title: z.string(), - description: z.string(), - date: z.coerce.date(), - products: z - .array(reference("products")) - .default([]) - .describe( - "An array of products to associate this changelog entry with. You may omit the product named after the folder this entry is in.", - ), -}); +export const changelogSchema = ({ image }: SchemaContext) => + z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date(), + products: z + .array(reference("products")) + .default([]) + .describe( + "An array of products to associate this changelog entry with. You may omit the product named after the folder this entry is in.", + ), + cover: image().optional(), + }); From 6c5e6a737912bcee7b8ded1123bb8ef7c3dcd64c Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Wed, 26 Feb 2025 16:37:33 +0000 Subject: [PATCH 03/10] Support changelog entries --- src/components/overrides/Head.astro | 6 +++++- src/pages/changelog/[...slug].astro | 1 + src/util/props.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/overrides/Head.astro b/src/components/overrides/Head.astro index 645a064f5ac323b..2b8d223ffd43fe6 100644 --- a/src/components/overrides/Head.astro +++ b/src/components/overrides/Head.astro @@ -6,6 +6,7 @@ import "tippy.js/dist/tippy.css"; import { getEntry } from "astro:content"; import { getOgImage } from "~/util/props"; +import { CollectionEntry } from "astro:content"; // grab the current top-level folder. Remove . characters for 1.1.1.1 URL const currentSection = Astro.url.pathname.split("/")[1].replaceAll(".", ""); @@ -201,7 +202,10 @@ if (Astro.props.entry.data.external_link) { }); } -const ogImagePath = await getOgImage(Astro.props.entry); +const ogImagePath = await getOgImage( + (Astro.props.originalEntry as CollectionEntry<"changelog">) ?? + Astro.props.entry, +); const ogImageUrl = new URL(ogImagePath, Astro.url.origin).toString(); const ogImageTags = [ diff --git a/src/pages/changelog/[...slug].astro b/src/pages/changelog/[...slug].astro index 7f9211d33590388..2e2e1f1987c7744 100644 --- a/src/pages/changelog/[...slug].astro +++ b/src/pages/changelog/[...slug].astro @@ -36,6 +36,7 @@ const props = { headings, hideTitle: true, hideBreadcrumbs: true, + originalEntry: note, } as StarlightPageProps; --- diff --git a/src/util/props.ts b/src/util/props.ts index 51e6c66edefc0f4..756f671642125c5 100644 --- a/src/util/props.ts +++ b/src/util/props.ts @@ -104,7 +104,7 @@ const PRODUCT_AREA_OG_IMAGES: Record = { "application security": "/core-services-preview.png", }; -export async function getOgImage(entry: CollectionEntry<"docs">) { +export async function getOgImage(entry: CollectionEntry<"docs" | "changelog">) { if (entry.data.cover) { if (!entry.data.cover.src) { throw new Error( From 413597759b2817530761bfeec3a47f3ecd5d84fb Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Wed, 26 Feb 2025 16:54:06 +0000 Subject: [PATCH 04/10] changelog default image --- src/util/props.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/props.ts b/src/util/props.ts index 756f671642125c5..87ea97cee87c8bf 100644 --- a/src/util/props.ts +++ b/src/util/props.ts @@ -120,16 +120,16 @@ export async function getOgImage(entry: CollectionEntry<"docs" | "changelog">) { return image.src; } + if (entry.collection === "changelog") { + return CHANGELOG_OG_IMAGE; + } + const section = entry.id.split("/").filter(Boolean).at(0); if (!section) { return DEFAULT_OG_IMAGE; } - if (section === "changelog") { - return CHANGELOG_OG_IMAGE; - } - const product = await getEntry("products", section); if (product && product.data.product.group) { From 6d7ab0fd9918e0d5a37c64220f6e55ef44b7af4a Mon Sep 17 00:00:00 2001 From: Kody Jackson Date: Wed, 26 Feb 2025 11:46:53 -0600 Subject: [PATCH 05/10] Remove example that doesn't make sense --- src/content/docs/workers/index.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/docs/workers/index.mdx b/src/content/docs/workers/index.mdx index adc5a03937a0bf0..c6f9540bbcb2473 100644 --- a/src/content/docs/workers/index.mdx +++ b/src/content/docs/workers/index.mdx @@ -7,7 +7,6 @@ sidebar: head: - tag: title content: Cloudflare Workers -cover: ~/assets/images/workers/kv-write.svg --- import { From c3e39975fe501bd77361abf185e18c65b5187d3d Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Wed, 26 Feb 2025 19:59:40 +0000 Subject: [PATCH 06/10] fix type import --- src/components/overrides/Head.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/overrides/Head.astro b/src/components/overrides/Head.astro index 2b8d223ffd43fe6..c487d6d94c5cb26 100644 --- a/src/components/overrides/Head.astro +++ b/src/components/overrides/Head.astro @@ -6,7 +6,7 @@ import "tippy.js/dist/tippy.css"; import { getEntry } from "astro:content"; import { getOgImage } from "~/util/props"; -import { CollectionEntry } from "astro:content"; +import type { CollectionEntry } from "astro:content"; // grab the current top-level folder. Remove . characters for 1.1.1.1 URL const currentSection = Astro.url.pathname.split("/")[1].replaceAll(".", ""); From b064b21fe3078965a51ac4f0267658869adf38a1 Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Thu, 27 Feb 2025 00:44:03 +0000 Subject: [PATCH 07/10] move to new file --- src/components/overrides/Head.astro | 2 +- src/util/og.ts | 55 +++++++++++++++++++++++++++++ src/util/props.ts | 55 ----------------------------- 3 files changed, 56 insertions(+), 56 deletions(-) create mode 100644 src/util/og.ts diff --git a/src/components/overrides/Head.astro b/src/components/overrides/Head.astro index c487d6d94c5cb26..695c6142e67d782 100644 --- a/src/components/overrides/Head.astro +++ b/src/components/overrides/Head.astro @@ -5,7 +5,7 @@ import { differenceInCalendarDays } from "date-fns"; import "tippy.js/dist/tippy.css"; import { getEntry } from "astro:content"; -import { getOgImage } from "~/util/props"; +import { getOgImage } from "~/util/og"; import type { CollectionEntry } from "astro:content"; // grab the current top-level folder. Remove . characters for 1.1.1.1 URL diff --git a/src/util/og.ts b/src/util/og.ts new file mode 100644 index 000000000000000..68ff8a5b6347698 --- /dev/null +++ b/src/util/og.ts @@ -0,0 +1,55 @@ +import { getImage } from "astro:assets"; +import { type CollectionEntry, getEntry } from "astro:content"; + +const DEFAULT_OG_IMAGE = "/cf-twitter-card.png"; + +const CHANGELOG_OG_IMAGE = "/changelog-preview.png"; + +const PRODUCT_AREA_OG_IMAGES: Record = { + "cloudflare essentials": "/core-services-preview.png", + "cloudflare one": "/zt-preview.png", + "developer platform": "/dev-products-preview.png", + "network security": "/core-services-preview.png", + "application performance": "/core-services-preview.png", + "application security": "/core-services-preview.png", +}; + +export async function getOgImage(entry: CollectionEntry<"docs" | "changelog">) { + if (entry.data.cover) { + if (!entry.data.cover.src) { + throw new Error( + `${entry.id} has a cover property in frontmatter that is not a valid image path`, + ); + } + + const image = await getImage({ + src: entry.data.cover, + format: "png", + }); + + return image.src; + } + + if (entry.collection === "changelog") { + return CHANGELOG_OG_IMAGE; + } + + const section = entry.id.split("/").filter(Boolean).at(0); + + if (!section) { + return DEFAULT_OG_IMAGE; + } + + const product = await getEntry("products", section); + + if (product && product.data.product.group) { + const image = + PRODUCT_AREA_OG_IMAGES[product.data.product.group.toLowerCase()]; + + if (image) { + return image; + } + } + + return DEFAULT_OG_IMAGE; +} diff --git a/src/util/props.ts b/src/util/props.ts index 87ea97cee87c8bf..c19e5a511fd6622 100644 --- a/src/util/props.ts +++ b/src/util/props.ts @@ -4,8 +4,6 @@ import he from "he"; import { remark } from "remark"; import strip from "strip-markdown"; import { rehypeExternalLinksOptions } from "~/plugins/rehype/external-links"; -import { getImage } from "astro:assets"; -import { getEntry, type CollectionEntry } from "astro:content"; type TableOfContentsItems = NonNullable["items"]; @@ -90,56 +88,3 @@ export async function generateDescription({ ?.replaceAll(rehypeExternalLinksOptions.content.value, "") .trim(); } - -const DEFAULT_OG_IMAGE = "/cf-twitter-card.png"; - -const CHANGELOG_OG_IMAGE = "/changelog-preview.png"; - -const PRODUCT_AREA_OG_IMAGES: Record = { - "cloudflare essentials": "/core-services-preview.png", - "cloudflare one": "/zt-preview.png", - "developer platform": "/dev-products-preview.png", - "network security": "/core-services-preview.png", - "application performance": "/core-services-preview.png", - "application security": "/core-services-preview.png", -}; - -export async function getOgImage(entry: CollectionEntry<"docs" | "changelog">) { - if (entry.data.cover) { - if (!entry.data.cover.src) { - throw new Error( - `${entry.id} has a cover property in frontmatter that is not a valid image path`, - ); - } - - const image = await getImage({ - src: entry.data.cover, - format: "png", - }); - - return image.src; - } - - if (entry.collection === "changelog") { - return CHANGELOG_OG_IMAGE; - } - - const section = entry.id.split("/").filter(Boolean).at(0); - - if (!section) { - return DEFAULT_OG_IMAGE; - } - - const product = await getEntry("products", section); - - if (product && product.data.product.group) { - const image = - PRODUCT_AREA_OG_IMAGES[product.data.product.group.toLowerCase()]; - - if (image) { - return image; - } - } - - return DEFAULT_OG_IMAGE; -} From 4ad3c03f0c628d21fecbe08de2bf12a5827e5d6c Mon Sep 17 00:00:00 2001 From: kodster28 Date: Thu, 27 Feb 2025 07:03:26 -0600 Subject: [PATCH 08/10] Add test image to R2 entry --- .../r2/2025-02-14-r2-super-slurper-faster-migrations.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/content/changelog/r2/2025-02-14-r2-super-slurper-faster-migrations.mdx b/src/content/changelog/r2/2025-02-14-r2-super-slurper-faster-migrations.mdx index c9b893fec24a068..a8d00d6de1755d8 100644 --- a/src/content/changelog/r2/2025-02-14-r2-super-slurper-faster-migrations.mdx +++ b/src/content/changelog/r2/2025-02-14-r2-super-slurper-faster-migrations.mdx @@ -4,6 +4,7 @@ description: Super Slurper now transfers data from cloud object storage provider products: - r2 date: 2025-02-14T19:00:00Z +cover: ~/assets/images/r2/slurper-objects-over-time-border.png --- [Super Slurper](/r2/data-migration/super-slurper/) now transfers data from cloud object storage providers like AWS S3 and Google Cloud Storage to [Cloudflare R2](/r2/) up to 5x faster than it did before. From 0d7ae4cc054c2424d4ff143790da651e1c7e617d Mon Sep 17 00:00:00 2001 From: kodster28 Date: Thu, 27 Feb 2025 07:08:47 -0600 Subject: [PATCH 09/10] Update property name --- .../r2/2025-02-14-r2-super-slurper-faster-migrations.mdx | 1 - src/schemas/base.ts | 2 +- src/schemas/changelog.ts | 2 +- src/util/og.ts | 8 ++++---- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/content/changelog/r2/2025-02-14-r2-super-slurper-faster-migrations.mdx b/src/content/changelog/r2/2025-02-14-r2-super-slurper-faster-migrations.mdx index a8d00d6de1755d8..c9b893fec24a068 100644 --- a/src/content/changelog/r2/2025-02-14-r2-super-slurper-faster-migrations.mdx +++ b/src/content/changelog/r2/2025-02-14-r2-super-slurper-faster-migrations.mdx @@ -4,7 +4,6 @@ description: Super Slurper now transfers data from cloud object storage provider products: - r2 date: 2025-02-14T19:00:00Z -cover: ~/assets/images/r2/slurper-objects-over-time-border.png --- [Super Slurper](/r2/data-migration/super-slurper/) now transfers data from cloud object storage providers like AWS S3 and Google Cloud Storage to [Cloudflare R2](/r2/) up to 5x faster than it did before. diff --git a/src/schemas/base.ts b/src/schemas/base.ts index 1bf6892d96fa040..50e0ba150a46757 100644 --- a/src/schemas/base.ts +++ b/src/schemas/base.ts @@ -15,7 +15,7 @@ const spotlightAuthorDetails = z export const baseSchema = ({ image }: SchemaContext) => z.object({ - cover: image().optional(), + preview_image: image().optional(), pcx_content_type: z .union([ z.literal("overview"), diff --git a/src/schemas/changelog.ts b/src/schemas/changelog.ts index ab2ac7c320dc5cf..a5f366aff1f9468 100644 --- a/src/schemas/changelog.ts +++ b/src/schemas/changelog.ts @@ -12,5 +12,5 @@ export const changelogSchema = ({ image }: SchemaContext) => .describe( "An array of products to associate this changelog entry with. You may omit the product named after the folder this entry is in.", ), - cover: image().optional(), + preview_image: image().optional(), }); diff --git a/src/util/og.ts b/src/util/og.ts index 68ff8a5b6347698..260d166b9fb7b0a 100644 --- a/src/util/og.ts +++ b/src/util/og.ts @@ -15,15 +15,15 @@ const PRODUCT_AREA_OG_IMAGES: Record = { }; export async function getOgImage(entry: CollectionEntry<"docs" | "changelog">) { - if (entry.data.cover) { - if (!entry.data.cover.src) { + if (entry.data.preview_image) { + if (!entry.data.preview_image.src) { throw new Error( - `${entry.id} has a cover property in frontmatter that is not a valid image path`, + `${entry.id} has a preview_image property in frontmatter that is not a valid image path`, ); } const image = await getImage({ - src: entry.data.cover, + src: entry.data.preview_image, format: "png", }); From d1a10ebe51a3f9f2be3359266c09d0a7be22e2f5 Mon Sep 17 00:00:00 2001 From: kodster28 Date: Thu, 27 Feb 2025 07:24:05 -0600 Subject: [PATCH 10/10] Add style guide entry --- .../frontmatter/custom-properties.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/content/docs/style-guide/frontmatter/custom-properties.mdx diff --git a/src/content/docs/style-guide/frontmatter/custom-properties.mdx b/src/content/docs/style-guide/frontmatter/custom-properties.mdx new file mode 100644 index 000000000000000..c1534710d9286f7 --- /dev/null +++ b/src/content/docs/style-guide/frontmatter/custom-properties.mdx @@ -0,0 +1,17 @@ +--- +title: Custom properties +sidebar: + order: 4 +--- + +import { Type, MetaInfo } from "~/components"; + +We have added specific custom [frontmatter](/style-guide/frontmatter/) to meet specific needs. + +- `difficulty` : Difficulty is displayed as a column in the [ListTutorials component](/style-guide/components/list-tutorials/). +- `external_link` : Path to another page in our docs or elsewhere. Used to add a crosslink entry to the lefthand navigation sidebar. +- `pcx_content_type` : The purpose of the page, and defined through specific pages in [Content strategy](/style-guide/documentation-content-strategy/content-types/). +- `preview_image` : An `src` path to the image that you want to use as a custom preview image for social sharing. +- `products` : The names of related products, which show on some grids for Examples, [Tutorials](/style-guide/documentation-content-strategy/content-types/tutorial/), and [Reference Architectures](/style-guide/documentation-content-strategy/content-types/reference-architecture/) +- `tags` : A group of related keywords relating to the purpose of the page. +- `updated` : This is used to automatically add the [LastReviewed component](/style-guide/components/last-reviewed/).