diff --git a/news/changelog-1.11.md b/news/changelog-1.11.md index 5a46cc678c2..886550db8c3 100644 --- a/news/changelog-1.11.md +++ b/news/changelog-1.11.md @@ -38,6 +38,10 @@ All changes included in 1.11: - ([#14982](https://github.com/quarto-dev/quarto-cli/issues/14982)): Fix a footnote in document metadata, such as `title` or `author`, making PDF and Typst output shift heading levels, which turned the first level-1 heading into the document title. +### `revealjs` + +- ([#14996](https://github.com/quarto-dev/quarto-cli/pull/14996)): Fix `controls: auto` and `previewLinks: auto` set at the top level of a revealjs document's front matter producing a presentation that fails to load, because `auto` was written unquoted into the generated JavaScript. + ### `typst` - ([#14847](https://github.com/quarto-dev/quarto-cli/pull/14847)): Fix `toc_title` auto-fallback in typst outline template that was ignoring the computed fallback value when `toc_title` is `none`. @@ -57,6 +61,10 @@ All changes included in 1.11: - ([#14974](https://github.com/quarto-dev/quarto-cli/issues/14974)): Fix math in `.llms.md` files from `llms-txt` coming out garbled with the default `mathjax` method, and with `katex` and `webtex`. Math is now written as `$...$` and `$$...$$` with the `mathjax`, `katex`, `webtex` and `mathml` methods. +## Lua API + +- ([#14894](https://github.com/quarto-dev/quarto-cli/pull/14894)): Fix `quarto.version` and `quarto.config.version()` crashing filters with `table expected, got string` when the version string contains semver build metadata (such as a distro packager's revision suffix) or otherwise does not start with a digit. + ## Commands ### `call` @@ -90,12 +98,9 @@ All changes included in 1.11: - ([#13966](https://github.com/quarto-dev/quarto-cli/issues/13966)): Fix doubled newlines in HTML output of stdout and stderr containing ANSI escape codes. -## Lua API - -- ([#14894](https://github.com/quarto-dev/quarto-cli/pull/14894)): Fix `quarto.version` and `quarto.config.version()` crashing filters with `table expected, got string` when the version string contains semver build metadata (such as a distro packager's revision suffix) or otherwise does not start with a digit. - ## Other fixes and improvements +- ([#9864](https://github.com/quarto-dev/quarto-cli/issues/9864), [#11139](https://github.com/quarto-dev/quarto-cli/issues/11139)): Fix metadata defined both in a document's front matter and in `_quarto.yml`, a directory `_metadata.yml` or `metadata-files` reaching Pandoc templates and Lua filters with only the document's values. The values are now merged, and the document's values still take precedence for single values. Lists are combined instead of replaced, so a document can no longer drop entries that the project, a directory `_metadata.yml` or `metadata-files` add to a list such as `keywords`. (author: @jkrumbiegel) - ([#14775](https://github.com/quarto-dev/quarto-cli/issues/14775)): Fix a crash when the `QUARTO_R` environment variable is set to a malformed path. Quarto now warns and falls back to other R lookup methods. - ([#14865](https://github.com/quarto-dev/quarto-cli/issues/14865)): Fix internal links in a preview being treated as external when the preview is reached through a proxy, such as on Posit Workbench. Links are now classified against the origin the browser sees. - ([#14878](https://github.com/quarto-dev/quarto-cli/pull/14878)): Add `az` (Azerbaijani) language translation. (author: @abdanar) diff --git a/src/command/render/pandoc.ts b/src/command/render/pandoc.ts index 10293088d5e..067580e221b 100644 --- a/src/command/render/pandoc.ts +++ b/src/command/render/pandoc.ts @@ -1177,12 +1177,31 @@ export async function runPandoc( if (key === kTheme && isRevealjsOutput(options.format.pandoc)) { continue; } + // - controls and previewLinks are replaced for revealjs when not a boolean (e.g. 'auto'), which is not a valid reveal.js value + if ( + (key === "controls" || key === "previewLinks") && + typeof engineMetadata[key] !== "boolean" && + isRevealjsOutput(options.format.pandoc) + ) { + continue; + } // - categories are handled specifically already for website projects with a metadata override and should not be overridden by user input if (key === kFieldCategories && projectIsWebsite(options.project)) { continue; } - // perform the override - pandocMetadata[key] = engineMetadata[key]; + // a key that execution left unchanged keeps what other sources merged + // into it, with the document's own values on top; a key holding inline + // expressions takes the executed value as a whole + if (ld.isEqual(options.unexecutedMetadata?.[key], engineMetadata[key])) { + // wrapped so that arrays and scalars are concatenated or replaced, + // not merged by index + pandocMetadata[key] = mergeConfigs( + { value: pandocMetadata[key] }, + { value: options.unexecutedMetadata?.[key] }, + ).value; + } else { + pandocMetadata[key] = engineMetadata[key]; + } } } diff --git a/src/command/render/render.ts b/src/command/render/render.ts index 622a1f58775..15c5b5a4b60 100644 --- a/src/command/render/render.ts +++ b/src/command/render/render.ts @@ -194,6 +194,7 @@ export async function renderPandoc( args: recipe.args, services: context.options.services, metadata: executeResult.metadata, + unexecutedMetadata: context.target.metadata, quiet, flags: context.options.flags, }; diff --git a/src/command/render/types.ts b/src/command/render/types.ts index 2826cdf9f6e..7b70484aba0 100644 --- a/src/command/render/types.ts +++ b/src/command/render/types.ts @@ -214,6 +214,9 @@ export interface PandocOptions { // extra metadata to merge metadata?: Metadata; + // the input's metadata as written, before execution resolved inline expressions + unexecutedMetadata?: Metadata; + // optional execution engine executionEngine?: string; diff --git a/tests/docs/metadata-merge-cli/cli-metadata-file.qmd b/tests/docs/metadata-merge-cli/cli-metadata-file.qmd new file mode 100644 index 00000000000..925cf2331a2 --- /dev/null +++ b/tests/docs/metadata-merge-cli/cli-metadata-file.qmd @@ -0,0 +1,25 @@ +--- +title: Front matter and --metadata-file +shared: document-value +custom: + shared: document-value + from-document: document-value +keywords: + - document-keyword +--- + +::: {#shared} +{{< meta shared >}} +::: + +::: {#custom-shared} +{{< meta custom.shared >}} +::: + +::: {#custom-from-document} +{{< meta custom.from-document >}} +::: + +::: {#custom-from-metadata-file} +{{< meta custom.from-metadata-file >}} +::: diff --git a/tests/docs/metadata-merge-cli/cli-metadata-file.yml b/tests/docs/metadata-merge-cli/cli-metadata-file.yml new file mode 100644 index 00000000000..73820f4c8c9 --- /dev/null +++ b/tests/docs/metadata-merge-cli/cli-metadata-file.yml @@ -0,0 +1,6 @@ +shared: metadata-file-value +custom: + shared: metadata-file-value + from-metadata-file: metadata-file-value +keywords: + - metadata-file-keyword diff --git a/tests/docs/project/metadata-merge/.gitignore b/tests/docs/project/metadata-merge/.gitignore new file mode 100644 index 00000000000..ad293093b07 --- /dev/null +++ b/tests/docs/project/metadata-merge/.gitignore @@ -0,0 +1,2 @@ +/.quarto/ +**/*.quarto_ipynb diff --git a/tests/docs/project/metadata-merge/_quarto.yml b/tests/docs/project/metadata-merge/_quarto.yml new file mode 100644 index 00000000000..22c38d61c24 --- /dev/null +++ b/tests/docs/project/metadata-merge/_quarto.yml @@ -0,0 +1,22 @@ +project: + type: default + +format: + html: + template: template.html + +keywords: + - project-keyword + - shared-keyword + +tags: + - project-tag + +params: + from-project: project-value + +custom: + from-project: project-value + shared: project-value + list: + - project-item diff --git a/tests/docs/project/metadata-merge/knitr.qmd b/tests/docs/project/metadata-merge/knitr.qmd new file mode 100644 index 00000000000..e01da0db8c2 --- /dev/null +++ b/tests/docs/project/metadata-merge/knitr.qmd @@ -0,0 +1,26 @@ +--- +title: Knitr document +keywords: + - document-keyword + - shared-keyword +params: + from-document: document-value +custom: + from-document: document-value + shared: document-value + list: + - document-item +tags: + - '`{r} "project-tag"`' + - document-tag +evaluated: '`{r} paste0("evaluated", "-value")`' +--- + +```{r} +#| echo: false +invisible(1) +``` + +::: {#lua-params} +{{< meta params.from-project >}} +::: diff --git a/tests/docs/project/metadata-merge/markdown.qmd b/tests/docs/project/metadata-merge/markdown.qmd new file mode 100644 index 00000000000..a7b8680aeba --- /dev/null +++ b/tests/docs/project/metadata-merge/markdown.qmd @@ -0,0 +1,17 @@ +--- +title: Markdown document +keywords: + - document-keyword + - shared-keyword +params: + from-document: document-value +custom: + from-document: document-value + shared: document-value + list: + - document-item +--- + +::: {#lua-params} +{{< meta params.from-project >}} +::: diff --git a/tests/docs/project/metadata-merge/template.html b/tests/docs/project/metadata-merge/template.html new file mode 100644 index 00000000000..832a8fbba5f --- /dev/null +++ b/tests/docs/project/metadata-merge/template.html @@ -0,0 +1,9 @@ +KEYWORDS: [$for(keywords)$$it$$sep$, $endfor$] +TAGS: [$for(tags)$$it$$sep$, $endfor$] +PARAMS: [$params.from-project$] [$params.from-document$] +FROM-PROJECT: [$custom.from-project$] +SHARED: [$custom.shared$] +FROM-DOCUMENT: [$custom.from-document$] +CUSTOM-LIST: [$for(custom.list)$$it$$sep$, $endfor$] +EVALUATED: [$evaluated$] +$body$ diff --git a/tests/docs/smoke-all/metadata-merge/.gitignore b/tests/docs/smoke-all/metadata-merge/.gitignore new file mode 100644 index 00000000000..ad293093b07 --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/.gitignore @@ -0,0 +1,2 @@ +/.quarto/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/metadata-merge/book-chapter-title/.gitignore b/tests/docs/smoke-all/metadata-merge/book-chapter-title/.gitignore new file mode 100644 index 00000000000..7359d935c76 --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/book-chapter-title/.gitignore @@ -0,0 +1,4 @@ +/.quarto/ +_book/ + +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/metadata-merge/book-chapter-title/_quarto.yml b/tests/docs/smoke-all/metadata-merge/book-chapter-title/_quarto.yml new file mode 100644 index 00000000000..59a8fd4797a --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/book-chapter-title/_quarto.yml @@ -0,0 +1,12 @@ +project: + type: book + +book: + title: "Metadata merge book" + chapters: + - index.qmd + - chapter.qmd + +format: + html: + number-sections: true diff --git a/tests/docs/smoke-all/metadata-merge/book-chapter-title/chapter.qmd b/tests/docs/smoke-all/metadata-merge/book-chapter-title/chapter.qmd new file mode 100644 index 00000000000..bcf6ae7a0af --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/book-chapter-title/chapter.qmd @@ -0,0 +1,14 @@ +--- +title: Chapter from YAML +_quarto: + tests: + html: + ensureHtmlElements: + - ['h1.title span.chapter-number'] + - [] + ensureHtmlElementContents: + selectors: ['h1.title'] + matches: ['^\s*1\s+Chapter from YAML\s*$'] +--- + +A book chapter whose title comes from the YAML `title` key keeps the chapter number Quarto computes for it. The document's own `title` value must not replace the numbered title. diff --git a/tests/docs/smoke-all/metadata-merge/book-chapter-title/index.qmd b/tests/docs/smoke-all/metadata-merge/book-chapter-title/index.qmd new file mode 100644 index 00000000000..ff4bd80cfb7 --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/book-chapter-title/index.qmd @@ -0,0 +1,3 @@ +# Preface {.unnumbered} + +Home page of the fixture. The assertions live in `chapter.qmd`. diff --git a/tests/docs/smoke-all/metadata-merge/format-key-precedence.qmd b/tests/docs/smoke-all/metadata-merge/format-key-precedence.qmd new file mode 100644 index 00000000000..5a9879960f6 --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/format-key-precedence.qmd @@ -0,0 +1,18 @@ +--- +title: Top-level title +subtitle: Top-level subtitle +format: + html: + title: Format title + subtitle: Format subtitle +_quarto: + tests: + html: + ensureFileRegexMatches: + - + - '
Format subtitle
' + - [] +--- + +A key set both at the top level and under `format: html:` in the same document takes the format-level value in the output. The document is not executed (markdown engine), so this checks the merge order of document metadata on its own: the executed-front-matter substitution done before calling pandoc must not bring the top-level value back. diff --git a/tests/docs/smoke-all/metadata-merge/knitr-inline-yaml.qmd b/tests/docs/smoke-all/metadata-merge/knitr-inline-yaml.qmd new file mode 100644 index 00000000000..f4f005a6b02 --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/knitr-inline-yaml.qmd @@ -0,0 +1,26 @@ +--- +title: "Title `r 1 + 1`" +custom-key: "Custom `{r} 2 + 3`" +nested: + inner: "Nested `r 3 * 3`" +engine: knitr +_quarto: + tests: + html: + ensureFileRegexMatches: + - + - 'Custom 5
' + - 'Nested 9
' + - [] +--- + +Inline R expressions in YAML front matter are evaluated by knitr, and the evaluated values must reach pandoc: the standard `title`, a custom top-level key, and a nested key with no project-level counterpart. + +::: {#custom-key} +{{< meta custom-key >}} +::: + +::: {#nested-inner} +{{< meta nested.inner >}} +::: diff --git a/tests/docs/smoke-all/metadata-merge/metadata-files-extra.yml b/tests/docs/smoke-all/metadata-merge/metadata-files-extra.yml new file mode 100644 index 00000000000..73820f4c8c9 --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/metadata-files-extra.yml @@ -0,0 +1,6 @@ +shared: metadata-file-value +custom: + shared: metadata-file-value + from-metadata-file: metadata-file-value +keywords: + - metadata-file-keyword diff --git a/tests/docs/smoke-all/metadata-merge/metadata-files.qmd b/tests/docs/smoke-all/metadata-merge/metadata-files.qmd new file mode 100644 index 00000000000..57ae326f97c --- /dev/null +++ b/tests/docs/smoke-all/metadata-merge/metadata-files.qmd @@ -0,0 +1,40 @@ +--- +title: Front matter and metadata-files +metadata-files: + - metadata-files-extra.yml +shared: document-value +custom: + shared: document-value + from-document: document-value +keywords: + - document-keyword +_quarto: + tests: + html: + ensureFileRegexMatches: + - + - '' + - '