Skip to content

Commit b74bec2

Browse files
authored
Fix code copy button covering code annotation marker (#15136)
Since #13024 moved the copy button into a scaffold outside the pre, the rule that kept it clear of the annotation gutter matched nothing. On an annotated block the button sat over the marker of the first line and swallowed its hover or click. The rule now selects the button through the scaffold that holds an annotated pre, and the button takes the code block font size so its margin matches the gutter in any theme. - Target `.code-copy-outer-scaffold:has(pre.code-annotated)` for the button's position. - Set the margin to the gutter width, `calc(20px + 0.5em)` plus the default 5px. `em` keeps the value relative to the code font size; a percentage would resolve against the container width instead. - Add a test covering annotated and plain blocks, select mode, and a theme with a smaller code block font size. The scaffold stays because it keeps the button from scrolling with the code, which line numbers need (#13009, #5538). Annotated blocks inside callouts are still affected; see #13446. Fixes #13444
1 parent fbdb3da commit b74bec2

9 files changed

Lines changed: 154 additions & 2 deletions

‎news/changelog-1.11.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ All changes included in 1.11:
6161

6262
- ([#12452](https://github.com/quarto-dev/quarto-cli/issues/12452)): Fix repeated HTML escaping in Google Scholar metadata, including apostrophes in titles, special characters in authors and URLs, and nested reference metadata.
6363
- ([#12650](https://github.com/quarto-dev/quarto-cli/issues/12650)): Fix markdown formatting and HTML entities in a document title appearing literally in the appendix citation, the BibTeX citation, and the Google Scholar `citation_title`. (author: @jolars)
64+
- ([#13444](https://github.com/quarto-dev/quarto-cli/issues/13444)): Fix the code copy button covering the code annotation marker on the first line of an annotated code block, which stopped the annotation from showing on hover or click (regression introduced in v1.8.15).
6465
- ([#14684](https://github.com/quarto-dev/quarto-cli/issues/14684)): Add a "Skip to main content" link to Bootstrap-themed HTML output (documents, websites, books, dashboards) so keyboard users can bypass the navbar and sidebars.
6566
- ([#14973](https://github.com/quarto-dev/quarto-cli/issues/14973)): Fix Safari sometimes opening pages with a light and dark theme at the top instead of at the URL fragment target, and toggling the color scheme scrolling back to the top of the page.
6667
- ([#14976](https://github.com/quarto-dev/quarto-cli/pull/14976)): Fix KaTeX math not rendering on pages with Jupyter widgets when `html-math-method` is set with the object form (`method: katex` and `url`).

‎src/resources/formats/html/bootstrap/_bootstrap-rules.scss‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,8 +1997,12 @@ textarea {
19971997

19981998
// Code Annotation
19991999

2000-
.code-annotated .code-copy-button {
2001-
margin-right: 1.25em;
2000+
// The copy button is a sibling of div.sourceCode, so it takes the code font
2001+
// size here to share the gutter's em, then clears the gutter
2002+
// (calc(20px + 0.5em)) plus the default 5px margin. Its icon is sized in rem.
2003+
.code-copy-outer-scaffold:has(pre.code-annotated) > .code-copy-button {
2004+
font-size: $code-block-font-size;
2005+
margin-right: calc(20px + 0.5em + 5px);
20022006
margin-top: 0;
20032007
padding-bottom: 0;
20042008
padding-top: 3px;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
title: "Code copy button and code annotation marker (code block font size in percent)"
3+
format:
4+
html:
5+
theme: [default, code-annotation-copy-button-font-size-percent.scss]
6+
code-annotations: hover
7+
---
8+
9+
```{.python}
10+
import polars as pl # <1>
11+
```
12+
13+
1. This is a code block with a copy button and an annotation.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/*-- scss:defaults --*/
2+
$code-block-font-size: 75%;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
title: "Code copy button and code annotation marker (code block font size)"
3+
format:
4+
html:
5+
theme: [default, code-annotation-copy-button-font-size.scss]
6+
code-annotations: hover
7+
---
8+
9+
```{.python}
10+
import polars as pl # <1>
11+
```
12+
13+
1. This is a code block with a copy button and an annotation.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/*-- scss:defaults --*/
2+
$code-block-font-size: 0.75em;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: "Code copy button and code annotation marker (select)"
3+
format: html
4+
code-annotations: select
5+
---
6+
7+
```{.python}
8+
import polars as pl # <1>
9+
```
10+
11+
1. This is a code block with a copy button and an annotation.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: "Code copy button and code annotation marker"
3+
format: html
4+
code-annotations: hover
5+
---
6+
7+
```{.python}
8+
import polars as pl # <1>
9+
```
10+
11+
1. This is a code block with a copy button and an annotation.
12+
13+
A code block without annotations keeps the copy button in its default place.
14+
15+
```{.python}
16+
import polars as pl
17+
```
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { expect, Locator, test } from "@playwright/test";
2+
import { getUrl } from "../src/utils";
3+
4+
// Regression test for #13444. The code copy button sits in the top-right
5+
// corner of the code block, where the annotation gutter shows the marker of
6+
// an annotated first line. The button must stay clear of the gutter so the
7+
// marker can still be hovered (or clicked) to show its annotation.
8+
9+
const rightEdge = async (locator: Locator) => {
10+
const box = await locator.boundingBox();
11+
expect(box).not.toBeNull();
12+
return box!.x + box!.width;
13+
};
14+
15+
const testCases = [
16+
{ name: "hover", url: "html/code-annotation-copy-button.html" },
17+
{ name: "select", url: "html/code-annotation-copy-button-select.html" },
18+
{
19+
name: "hover with a custom code block font size",
20+
url: "html/code-annotation-copy-button-font-size.html",
21+
},
22+
{
23+
name: "hover with a code block font size in percent",
24+
url: "html/code-annotation-copy-button-font-size-percent.html",
25+
},
26+
];
27+
28+
test.describe("code copy button and code annotation marker", () => {
29+
for (const { name, url } of testCases) {
30+
test(`${name} — copy button does not cover the marker`, async ({ page }) => {
31+
await page.goto(getUrl(url), { waitUntil: "load" });
32+
33+
const scaffold = page.locator(".code-copy-outer-scaffold").filter({
34+
has: page.locator("pre.code-annotated"),
35+
});
36+
const anchor = scaffold.locator("button.code-annotation-anchor");
37+
await expect(anchor).toHaveCount(1);
38+
const box = await anchor.boundingBox();
39+
expect(box).not.toBeNull();
40+
const x = box!.x + box!.width / 2;
41+
const y = box!.y + box!.height / 2;
42+
43+
// Hovering the code block reveals the copy button, so check the marker
44+
// is still the top-most element at its own center while the button is
45+
// shown.
46+
await page.mouse.move(x, y);
47+
const hit = await page.evaluate(
48+
([px, py]) =>
49+
document.elementFromPoint(px, py)?.closest(
50+
".code-annotation-anchor, .code-copy-button",
51+
)?.className ?? null,
52+
[x, y],
53+
);
54+
expect(hit).toContain("code-annotation-anchor");
55+
56+
// The button sits the default 5px left of the gutter, whatever the code
57+
// block font size the gutter width is derived from.
58+
const gutterLeft = (await scaffold.locator(".code-annotation-gutter")
59+
.boundingBox())!.x;
60+
const buttonRight = await rightEdge(
61+
scaffold.locator(".code-copy-button"),
62+
);
63+
expect(Math.abs(gutterLeft - buttonRight - 5)).toBeLessThanOrEqual(1);
64+
65+
if (name === "select") {
66+
await page.mouse.click(x, y);
67+
}
68+
await expect(page.locator(".code-annotation-tip-content")).toBeVisible();
69+
});
70+
}
71+
72+
test("copy button of a block without annotations is unchanged", async ({
73+
page,
74+
}) => {
75+
await page.goto(getUrl("html/code-annotation-copy-button.html"), {
76+
waitUntil: "load",
77+
});
78+
79+
const scaffold = page.locator(".code-copy-outer-scaffold").filter({
80+
hasNot: page.locator("pre.code-annotated"),
81+
});
82+
await expect(scaffold).toHaveCount(1);
83+
const button = scaffold.locator(".code-copy-button");
84+
await expect(button).toHaveCSS("right", "0px");
85+
await expect(button).toHaveCSS("margin-right", "5px");
86+
expect((await rightEdge(scaffold)) - (await rightEdge(button)))
87+
.toBeCloseTo(5, 0);
88+
});
89+
});

0 commit comments

Comments
 (0)