From 9261f77616f244406f59ad711d9c8f8f13c2d5fe Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Tue, 27 Oct 2026 15:10:32 +0100 Subject: [PATCH 1/3] Respect code-copy for the citation BibTeX copy button The citation appendix added a copy button, its scaffold and the code-with-copy class to the BibTeX block unconditionally, since the block was made copyable in 0579ebc1b. With `code-copy: false`, clipboard.js and the ClipboardJS setup script are not emitted, so the button was rendered with its icon but did nothing. The appendix now gates the class, scaffold and button on the same resolved option as other code blocks. The resolution was inlined twice in format-html.ts; it moves to formatHasCodeCopy() in format-html-shared.ts so the appendix can share it without importing format-html.ts, which imports the bootstrap postprocessor that calls the appendix. createCodeBlock no longer adds code-with-copy itself, as the appendix is its only caller. --- src/format/html/format-html-appendix.ts | 18 ++++++++++++------ src/format/html/format-html-shared.ts | 12 +++++++++++- src/format/html/format-html.ts | 12 +++--------- .../2026/10/27/citation-code-copy-false.qmd | 19 +++++++++++++++++++ 4 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 tests/docs/smoke-all/2026/10/27/citation-code-copy-false.qmd diff --git a/src/format/html/format-html-appendix.ts b/src/format/html/format-html-appendix.ts index 26f5813683d..7560451955d 100644 --- a/src/format/html/format-html-appendix.ts +++ b/src/format/html/format-html-appendix.ts @@ -26,6 +26,7 @@ import { import { createCodeBlock, createCodeCopyButton, + formatHasCodeCopy, hasMarginCites, hasMarginRefs, insertFootnotesTitle, @@ -306,13 +307,18 @@ export async function processDocumentAppendix( const bibTexDiv = createCodeBlock(doc, cite.bibtex, "bibtex"); bibTexDiv.classList.add(kQuartoCiteBibtexClass); - const outerScaffold = doc.createElement("div"); - outerScaffold.classList.add("code-copy-outer-scaffold"); - outerScaffold.appendChild(bibTexDiv); - contentsDiv.appendChild(outerScaffold); + if (formatHasCodeCopy(format)) { + bibTexDiv.classList.add("code-with-copy"); + const outerScaffold = doc.createElement("div"); + outerScaffold.classList.add("code-copy-outer-scaffold"); + outerScaffold.appendChild(bibTexDiv); + contentsDiv.appendChild(outerScaffold); - const copyButton = createCodeCopyButton(doc, format); - outerScaffold.appendChild(copyButton); + const copyButton = createCodeCopyButton(doc, format); + outerScaffold.appendChild(copyButton); + } else { + contentsDiv.appendChild(bibTexDiv); + } } if (cite?.html) { diff --git a/src/format/html/format-html-shared.ts b/src/format/html/format-html-shared.ts index e9f31e4f12f..d02fcc2ecb1 100644 --- a/src/format/html/format-html-shared.ts +++ b/src/format/html/format-html-shared.ts @@ -73,6 +73,17 @@ export const kCitation = "citation"; export const kDocumentCss = "document-css"; export const kBootstrapDependencyName = "bootstrap"; +// whether code copy buttons (and clipboard.js) are enabled for this format. +// 'code-copy' is on by default unless the format (e.g. 'minimal') turns it off +export function formatHasCodeCopy( + format: Format, + enabledByDefault = format.metadata[kMinimal] !== true, +): boolean { + return enabledByDefault + ? format.metadata[kCodeCopy] !== false + : !!format.metadata[kCodeCopy]; +} + export const clipboardDependency = () => { const dependency: FormatDependency = { name: "clipboard" }; dependency.scripts = []; @@ -492,7 +503,6 @@ export function createCodeBlock( ) { const preEl = doc.createElement("PRE"); preEl.classList.add("sourceCode"); - preEl.classList.add("code-with-copy"); const codeEl = doc.createElement("CODE"); codeEl.classList.add("sourceCode"); diff --git a/src/format/html/format-html.ts b/src/format/html/format-html.ts index aaf3e38cb4c..f1dbe90a7ac 100644 --- a/src/format/html/format-html.ts +++ b/src/format/html/format-html.ts @@ -67,12 +67,12 @@ import { bootstrapExtras } from "./format-html-bootstrap.ts"; import { clipboardDependency, createCodeCopyButton, + formatHasCodeCopy, kAnchorSections, kAxe, kBootstrapDependencyName, kCitationsHover, kCodeAnnotations, - kCodeCopy, kComments, kDocumentCss, kFootnotesHover, @@ -306,11 +306,7 @@ export async function htmlFormatExtras( } else { options.tabby = format.metadata[kTabsets] || false; } - if (featureDefaults.copyCode) { - options.copyCode = format.metadata[kCodeCopy] !== false; - } else { - options.copyCode = format.metadata[kCodeCopy] || false; - } + options.copyCode = formatHasCodeCopy(format, featureDefaults.copyCode); if (featureDefaults.anchors) { options.anchors = format.metadata[kAnchorSections] !== false; } else { @@ -712,9 +708,7 @@ function htmlFormatPostprocessor( } // read options - const codeCopy = featureDefaults.copyCode - ? format.metadata[kCodeCopy] !== false - : format.metadata[kCodeCopy] || false; + const codeCopy = formatHasCodeCopy(format, featureDefaults.copyCode); const anchors = featureDefaults.anchors ? format.metadata[kAnchorSections] !== false diff --git a/tests/docs/smoke-all/2026/10/27/citation-code-copy-false.qmd b/tests/docs/smoke-all/2026/10/27/citation-code-copy-false.qmd new file mode 100644 index 00000000000..71c7d690fe3 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/27/citation-code-copy-false.qmd @@ -0,0 +1,19 @@ +--- +title: "Citation BibTeX block without code-copy" +author: "Author name" +date: "2025-01-01" +format: html +citation: true +code-copy: false +_quarto: + tests: + html: + ensureHtmlElements: + - + - '#quarto-citation pre.quarto-appendix-bibtex' + - + - '#quarto-citation button.code-copy-button' + - '#quarto-citation div.code-copy-outer-scaffold' +--- + +With `code-copy: false`, clipboard.js is not loaded, so a copy button on the BibTeX block in the citation appendix would do nothing. The BibTeX block is still rendered, but without the copy button or its scaffold, the same as other code blocks in the document. From c56a3af10c3827eacc5e0f0b02d5b5cc4ecf9467 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Tue, 27 Oct 2026 15:22:05 +0100 Subject: [PATCH 2/3] Derive the code-copy default from htmlFormatFeatureDefaults formatHasCodeCopy() re-derived the code-copy default from 'minimal', duplicating htmlFormatFeatureDefaults(). That function was private to format-html.ts, which the appendix cannot import without a cycle (format-html.ts -> bootstrap -> appendix). Moving it to format-html-shared.ts keeps the feature defaults in one place for every caller. --- src/format/html/format-html-shared.ts | 24 +++++++++++++++++++++--- src/format/html/format-html.ts | 18 +----------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/format/html/format-html-shared.ts b/src/format/html/format-html-shared.ts index d02fcc2ecb1..30456afc928 100644 --- a/src/format/html/format-html-shared.ts +++ b/src/format/html/format-html-shared.ts @@ -28,6 +28,8 @@ import { import { formatResourcePath } from "../../core/resources.ts"; import { Document, Element } from "../../core/deno-dom.ts"; import { normalizePath } from "../../core/path.ts"; +import { formatHasBootstrap } from "./format-html-info.ts"; +import { HtmlFormatFeatureDefaults } from "./format-html-types.ts"; // features that are enabled by default for 'html'. setting // all of these to false will yield the minimal html output @@ -73,11 +75,27 @@ export const kCitation = "citation"; export const kDocumentCss = "document-css"; export const kBootstrapDependencyName = "bootstrap"; -// whether code copy buttons (and clipboard.js) are enabled for this format. -// 'code-copy' is on by default unless the format (e.g. 'minimal') turns it off +export function htmlFormatFeatureDefaults( + format: Format, +): HtmlFormatFeatureDefaults { + const bootstrap = formatHasBootstrap(format); + const minimal = format.metadata[kMinimal] === true; + return { + tabby: !minimal && !bootstrap, + copyCode: !minimal, + anchors: !minimal, + hoverCitations: !minimal, + hoverFootnotes: !minimal, + figResponsive: !minimal, + codeAnnotations: !minimal, + hoverXrefs: !minimal, + }; +} + +// whether code copy buttons (and clipboard.js) are enabled for this format export function formatHasCodeCopy( format: Format, - enabledByDefault = format.metadata[kMinimal] !== true, + enabledByDefault = htmlFormatFeatureDefaults(format).copyCode, ): boolean { return enabledByDefault ? format.metadata[kCodeCopy] !== false diff --git a/src/format/html/format-html.ts b/src/format/html/format-html.ts index f1dbe90a7ac..e78a60a1a85 100644 --- a/src/format/html/format-html.ts +++ b/src/format/html/format-html.ts @@ -68,6 +68,7 @@ import { clipboardDependency, createCodeCopyButton, formatHasCodeCopy, + htmlFormatFeatureDefaults, kAnchorSections, kAxe, kBootstrapDependencyName, @@ -678,23 +679,6 @@ function htmlFormatFilterParams(format: Format) { }; } -function htmlFormatFeatureDefaults( - format: Format, -): HtmlFormatFeatureDefaults { - const bootstrap = formatHasBootstrap(format); - const minimal = format.metadata[kMinimal] === true; - return { - tabby: !minimal && !bootstrap, - copyCode: !minimal, - anchors: !minimal, - hoverCitations: !minimal, - hoverFootnotes: !minimal, - figResponsive: !minimal, - codeAnnotations: !minimal, - hoverXrefs: !minimal, - }; -} - function htmlFormatPostprocessor( format: Format, featureDefaults?: HtmlFormatFeatureDefaults, From a89f507b4835318bd5ac7f754db00f9d48000219 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Tue, 27 Oct 2026 15:33:36 +0100 Subject: [PATCH 3/3] Add changelog entry for #15207 and name its test after the issue --- news/changelog-1.11.md | 1 + .../10/27/{citation-code-copy-false.qmd => issue-15207.qmd} | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename tests/docs/smoke-all/2026/10/27/{citation-code-copy-false.qmd => issue-15207.qmd} (97%) diff --git a/news/changelog-1.11.md b/news/changelog-1.11.md index 4e98d750868..06d62dd6e30 100644 --- a/news/changelog-1.11.md +++ b/news/changelog-1.11.md @@ -81,6 +81,7 @@ All changes included in 1.11: - ([#15125](https://github.com/quarto-dev/quarto-cli/pull/15125)): Fix Google Scholar metadata failing the render with a CSL JSON (`.json`) bibliography. - ([#15128](https://github.com/quarto-dev/quarto-cli/pull/15128)): Fix Google Scholar metadata missing references with a YAML bibliography not wrapped in `---` delimiters. - ([#15131](https://github.com/quarto-dev/quarto-cli/issues/15131)): Fix the light/dark toggle appearing, with no dark styles to switch to, when the `dark` brand file holds only comments and is ignored. +- ([#15207](https://github.com/quarto-dev/quarto-cli/issues/15207)): Fix the BibTeX citation in the HTML appendix showing a copy button that does nothing when `code-copy: false` is set. ### `pdf` diff --git a/tests/docs/smoke-all/2026/10/27/citation-code-copy-false.qmd b/tests/docs/smoke-all/2026/10/27/issue-15207.qmd similarity index 97% rename from tests/docs/smoke-all/2026/10/27/citation-code-copy-false.qmd rename to tests/docs/smoke-all/2026/10/27/issue-15207.qmd index 71c7d690fe3..b0ae9baea91 100644 --- a/tests/docs/smoke-all/2026/10/27/citation-code-copy-false.qmd +++ b/tests/docs/smoke-all/2026/10/27/issue-15207.qmd @@ -16,4 +16,4 @@ _quarto: - '#quarto-citation div.code-copy-outer-scaffold' --- -With `code-copy: false`, clipboard.js is not loaded, so a copy button on the BibTeX block in the citation appendix would do nothing. The BibTeX block is still rendered, but without the copy button or its scaffold, the same as other code blocks in the document. +With `code-copy: false`, clipboard.js is not loaded, so a copy button on the BibTeX block in the citation appendix would do nothing. The BibTeX block is still rendered, but without the copy button or its scaffold, the same as other code blocks in the document (#15207).