diff --git a/src/project/types/website/website-llms.ts b/src/project/types/website/website-llms.ts index 5b8431f31e..b6b801953d 100644 --- a/src/project/types/website/website-llms.ts +++ b/src/project/types/website/website-llms.ts @@ -24,16 +24,24 @@ import { websiteTitle, } from "./website-config.ts"; import { inputFileHref } from "./website-shared.ts"; -import { isDraftVisible, isProjectDraft, projectDraftMode } from "./website-utils.ts"; +import { + isDraftVisible, + isProjectDraft, + projectDraftMode, +} from "./website-utils.ts"; import { resolveInputTargetForOutputFile } from "../../project-index.ts"; -import { Format } from "../../../config/types.ts"; +import { Format, Metadata } from "../../../config/types.ts"; +import { kWebsite } from "./website-constants.ts"; /** * Compute the output HTML file path from the source file. * Uses inputFileHref to convert the relative source path to an HTML href, * then joins with the output directory. */ -function computeOutputFilePath(source: string, project: ProjectContext): string { +function computeOutputFilePath( + source: string, + project: ProjectContext, +): string { const outputDir = projectOutputDir(project); const sourceRelative = relative(project.dir, source); // inputFileHref returns "/path/to/file.html" - strip leading / and join with output dir @@ -176,7 +184,10 @@ ${main.innerHTML} * Restores original code text (with annotation markers) and converts * the annotation definition list to an ordered list. */ -function preprocessAnnotatedCodeBlocks(doc: Document, container: Element): void { +function preprocessAnnotatedCodeBlocks( + doc: Document, + container: Element, +): void { // Restore original code text in annotated code blocks. // The llms-code-annotations.lua filter saves the original text // (before code-annotation.lua strips markers) as a data attribute. @@ -301,8 +312,17 @@ export async function updateLlmsTxt( return; } - const siteTitle = websiteTitle(context.config) || "Untitled"; - const siteDesc = websiteDescription(context.config) || ""; + // Read resolved site title/description from the first output file's format + // metadata. The metadata markdown pipeline in website-meta.ts writes resolved + // values (with shortcodes expanded) back into format.metadata during per-page + // rendering, and these flow through to ProjectOutputFile.format. + const firstFileMeta = outputFiles.length > 0 + ? outputFiles[0].format.metadata[kWebsite] as Metadata | undefined + : undefined; + const siteTitle = (firstFileMeta?.title as string) || + websiteTitle(context.config) || "Untitled"; + const siteDesc = (firstFileMeta?.description as string) || + websiteDescription(context.config) || ""; const baseUrl = websiteBaseurl(context.config); const draftMode = projectDraftMode(context); @@ -338,7 +358,9 @@ export async function updateLlmsTxt( // Extract title from the format metadata or use filename const title = (file.format.metadata?.title as string) || basename(file.file, ".html"); - const relativePath = pathWithForwardSlashes(relative(outputDir, llmsPath)); + const relativePath = pathWithForwardSlashes( + relative(outputDir, llmsPath), + ); const filePath = baseUrl ? (baseUrl.endsWith("/") ? baseUrl : baseUrl + "/") + relativePath : relativePath; diff --git a/src/project/types/website/website-meta.ts b/src/project/types/website/website-meta.ts index b37985436e..b70ac230d6 100644 --- a/src/project/types/website/website-meta.ts +++ b/src/project/types/website/website-meta.ts @@ -431,6 +431,8 @@ const kTwitterDesc = "quarto-twittercarddesc"; const kOgTitle = "quarto-ogcardtitle"; const kOgDesc = "quarto-ogcardddesc"; const kMetaSideNameId = "quarto-metasitename"; +const kMetaSiteDescId = "quarto-metasitedesc"; + function metaMarkdownPipeline(format: Format, extras: FormatExtras) { const resolvedTitle = computePageTitle(format); @@ -504,7 +506,13 @@ function metaMarkdownPipeline(format: Format, extras: FormatExtras) { processRendered(rendered: Record, doc: Document) { const renderedEl = rendered[kMetaSideNameId]; if (renderedEl) { - // Update the document title + // Write resolved title back into format.metadata so it flows + // through to ProjectOutputFile.format for post-render consumers + const siteMeta = format.metadata[kWebsite] as Metadata; + if (siteMeta) { + siteMeta[kTitle] = renderedEl.innerText; + } + // Update the og:site_name meta tag const el = doc.querySelector( `meta[property="og:site_name"]`, ); @@ -555,9 +563,32 @@ function metaMarkdownPipeline(format: Format, extras: FormatExtras) { }, }; + const siteDescriptionHandler = { + getUnrendered() { + const siteMeta = format.metadata[kWebsite] as Metadata; + if (siteMeta && siteMeta[kDescription]) { + return { + inlines: { [kMetaSiteDescId]: siteMeta[kDescription] as string }, + }; + } + }, + processRendered(rendered: Record) { + const renderedEl = rendered[kMetaSiteDescId]; + if (renderedEl) { + // Write resolved description back into format.metadata so it flows + // through to ProjectOutputFile.format for post-render consumers + const siteMeta = format.metadata[kWebsite] as Metadata; + if (siteMeta) { + siteMeta[kDescription] = renderedEl.innerText; + } + } + }, + }; + return createMarkdownPipeline("quarto-meta-markdown", [ titleMetaHandler, siteTitleMetaHandler, descriptionMetaHandler, + siteDescriptionHandler, ]); } diff --git a/tests/docs/smoke-all/website/llms-txt-shortcode/.gitignore b/tests/docs/smoke-all/website/llms-txt-shortcode/.gitignore new file mode 100644 index 0000000000..1252e77997 --- /dev/null +++ b/tests/docs/smoke-all/website/llms-txt-shortcode/.gitignore @@ -0,0 +1,9 @@ +/.quarto/ +*.html +*.llms.md +llms.txt +search.json +site_libs/ +*_files/ + +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/website/llms-txt-shortcode/_environment b/tests/docs/smoke-all/website/llms-txt-shortcode/_environment new file mode 100644 index 0000000000..3ee57c7667 --- /dev/null +++ b/tests/docs/smoke-all/website/llms-txt-shortcode/_environment @@ -0,0 +1 @@ +LLMS_TEST_VAR="Resolved" diff --git a/tests/docs/smoke-all/website/llms-txt-shortcode/_quarto.yml b/tests/docs/smoke-all/website/llms-txt-shortcode/_quarto.yml new file mode 100644 index 0000000000..7613912ef7 --- /dev/null +++ b/tests/docs/smoke-all/website/llms-txt-shortcode/_quarto.yml @@ -0,0 +1,15 @@ +project: + type: website + output-dir: . + +website: + title: "Site Title {{< env LLMS_TEST_VAR >}}" + llms-txt: true + navbar: + left: + - href: index.qmd + text: Home + +format: + html: + theme: cosmo diff --git a/tests/docs/smoke-all/website/llms-txt-shortcode/index.qmd b/tests/docs/smoke-all/website/llms-txt-shortcode/index.qmd new file mode 100644 index 0000000000..a7b8707b54 --- /dev/null +++ b/tests/docs/smoke-all/website/llms-txt-shortcode/index.qmd @@ -0,0 +1,15 @@ +--- +title: "Home" +_quarto: + render-project: true + tests: + html: + ensureLlmsTxtExists: true + ensureLlmsTxtRegexMatches: + - ["^# Site Title Resolved"] + - ["\\{\\{< env"] +--- + +## Test Content + +This tests that shortcodes in website.title are resolved in llms.txt.