diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index d43a69482c4..da602c7a252 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -23,6 +23,6 @@ All changes included in 1.10: ## Other fixes and improvements +- ([#14306](https://github.com/quarto-dev/quarto-cli/issues/14306)): Fix website Open Graph metadata for posts to include the missing `og:type` and `og:url` tags. - ([#6651](https://github.com/quarto-dev/quarto-cli/issues/6651)): Fix dart-sass compilation failing in enterprise environments where `.bat` files are blocked by group policy. - ([#14255](https://github.com/quarto-dev/quarto-cli/issues/14255)): Fix shortcodes inside inline and display math expressions not being resolved. - diff --git a/src/project/types/website/website-meta.ts b/src/project/types/website/website-meta.ts index b70ac230d68..db6276f689c 100644 --- a/src/project/types/website/website-meta.ts +++ b/src/project/types/website/website-meta.ts @@ -8,6 +8,10 @@ import { Document, Element } from "../../../core/deno-dom.ts"; import { dirname, join, relative } from "../../../deno_ral/path.ts"; import { kAbstract, + kAuthor, + kAuthors, + kDate, + kDateModified, kDescription, kNumberSections, kSubtitle, @@ -49,9 +53,12 @@ import { imageSize } from "../../../core/image.ts"; import { writeMetaTag } from "../../../format/html/format-html-shared.ts"; import { joinUrl } from "../../../core/url.ts"; import { truncateText } from "../../../core/text.ts"; +import { synthesizeCitationUrl } from "../../../quarto-core/attribution/document.ts"; import { websiteImage } from "./website-config.ts"; const kCard = "card"; +const kType = "type"; +const kUrl = "url"; interface SocialMetadataProvider { key: string; @@ -118,6 +125,17 @@ export function metadataHtmlPostProcessor( return value; }, + resolveDefaults: (finalMetadata: Metadata) => { + if (finalMetadata[kType] === undefined) { + finalMetadata[kType] = openGraphType(format); + } + if (finalMetadata[kUrl] === undefined) { + const url = openGraphUrl(source, project, format); + if (url) { + finalMetadata[kUrl] = url; + } + } + }, }; // The twitter card provider @@ -261,6 +279,8 @@ function opengraphMetadata( kImageWidth, kLocale, kSiteName, + kType, + kUrl, ].forEach((key) => { if (openGraph[key] !== undefined) { metadata[key] = openGraph[key]; @@ -345,6 +365,28 @@ function resolveImageMetadata( } } +function openGraphType(format: Format) { + return format.metadata[kDate] !== undefined || + format.metadata[kDateModified] !== undefined || + format.metadata[kAuthor] !== undefined || + format.metadata[kAuthors] !== undefined + ? "article" + : "website"; +} + +function openGraphUrl( + source: string, + project: ProjectContext, + format: Format, +) { + return synthesizeCitationUrl( + source, + format.metadata, + format.pandoc["output-file"], + relative(dirname(source), project.dir), + ); +} + function mergedSiteAndDocumentData( key: string, format: Format, diff --git a/src/resources/types/schema-types.ts b/src/resources/types/schema-types.ts index e2933f641eb..95a65ed95f1 100644 --- a/src/resources/types/schema-types.ts +++ b/src/resources/types/schema-types.ts @@ -331,6 +331,8 @@ export type OpenGraphConfig = { provided in the `open-graph` metadata, Quarto will use the website or book `title` by default. */; locale?: string; /* Locale of open graph metadata */ + type?: string; /* Content to use for the `og:type` tag */ + url?: string; /* Content to use for the `og:url` tag */ } & SocialMetadata; export type PageFooter = { diff --git a/src/resources/types/zod/schema-types.ts b/src/resources/types/zod/schema-types.ts index 1471ea898f4..90a3b8a0464 100644 --- a/src/resources/types/zod/schema-types.ts +++ b/src/resources/types/zod/schema-types.ts @@ -269,6 +269,8 @@ export const ZodOpenGraphConfig = z.object({ "image-height": z.number(), locale: z.string(), "site-name": z.string(), + type: z.string(), + url: z.string(), }).strict().partial(); export const ZodPageFooter = z.object({ diff --git a/tests/docs/smoke-all/2026/04/02/og-type-url/_quarto.yml b/tests/docs/smoke-all/2026/04/02/og-type-url/_quarto.yml new file mode 100644 index 00000000000..0f1c3e2247c --- /dev/null +++ b/tests/docs/smoke-all/2026/04/02/og-type-url/_quarto.yml @@ -0,0 +1,9 @@ +project: + type: website + +website: + title: "Open Graph Type URL" + site-url: https://example.com + open-graph: true + +format: html diff --git a/tests/docs/smoke-all/2026/04/02/og-type-url/index.qmd b/tests/docs/smoke-all/2026/04/02/og-type-url/index.qmd new file mode 100644 index 00000000000..17325bcf838 --- /dev/null +++ b/tests/docs/smoke-all/2026/04/02/og-type-url/index.qmd @@ -0,0 +1,5 @@ +--- +title: "Home" +--- + +Home page. diff --git a/tests/docs/smoke-all/2026/04/02/og-type-url/posts/a-test-post/index.qmd b/tests/docs/smoke-all/2026/04/02/og-type-url/posts/a-test-post/index.qmd new file mode 100644 index 00000000000..7c25bdf562c --- /dev/null +++ b/tests/docs/smoke-all/2026/04/02/og-type-url/posts/a-test-post/index.qmd @@ -0,0 +1,17 @@ +--- +title: "A Test Post" +description: "A test post for Open Graph metadata." +date: "2026-04-02" +author: "Quarto Tester" +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - [] +--- + +This is a test post. diff --git a/tests/docs/smoke-all/2026/04/02/og-type-url/posts/override-og/index.qmd b/tests/docs/smoke-all/2026/04/02/og-type-url/posts/override-og/index.qmd new file mode 100644 index 00000000000..d3875775c81 --- /dev/null +++ b/tests/docs/smoke-all/2026/04/02/og-type-url/posts/override-og/index.qmd @@ -0,0 +1,21 @@ +--- +title: "Override Open Graph" +description: "A test post for Open Graph metadata overrides." +date: "2026-04-02" +author: "Quarto Tester" +website: + open-graph: + type: website + url: https://www.example.com/ +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - [] +--- + +This is a test post with overridden Open Graph metadata.