Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/changelog-1.11.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ All changes included in 1.11:

- ([#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.
- ([#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)
- ([#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).
- ([#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.
- ([#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.
- ([#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`).
Expand Down
8 changes: 6 additions & 2 deletions src/resources/formats/html/bootstrap/_bootstrap-rules.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1997,8 +1997,12 @@ textarea {

// Code Annotation

.code-annotated .code-copy-button {
margin-right: 1.25em;
// The copy button is a sibling of div.sourceCode, so it takes the code font
// size here to share the gutter's em, then clears the gutter
// (calc(20px + 0.5em)) plus the default 5px margin. Its icon is sized in rem.
.code-copy-outer-scaffold:has(pre.code-annotated) > .code-copy-button {
font-size: $code-block-font-size;
margin-right: calc(20px + 0.5em + 5px);
margin-top: 0;
padding-bottom: 0;
padding-top: 3px;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: "Code copy button and code annotation marker (code block font size in percent)"
format:
html:
theme: [default, code-annotation-copy-button-font-size-percent.scss]
code-annotations: hover
---

```{.python}
import polars as pl # <1>
```

1. This is a code block with a copy button and an annotation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/*-- scss:defaults --*/
$code-block-font-size: 75%;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: "Code copy button and code annotation marker (code block font size)"
format:
html:
theme: [default, code-annotation-copy-button-font-size.scss]
code-annotations: hover
---

```{.python}
import polars as pl # <1>
```

1. This is a code block with a copy button and an annotation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/*-- scss:defaults --*/
$code-block-font-size: 0.75em;
11 changes: 11 additions & 0 deletions tests/docs/playwright/html/code-annotation-copy-button-select.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: "Code copy button and code annotation marker (select)"
format: html
code-annotations: select
---

```{.python}
import polars as pl # <1>
```

1. This is a code block with a copy button and an annotation.
17 changes: 17 additions & 0 deletions tests/docs/playwright/html/code-annotation-copy-button.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: "Code copy button and code annotation marker"
format: html
code-annotations: hover
---

```{.python}
import polars as pl # <1>
```

1. This is a code block with a copy button and an annotation.

A code block without annotations keeps the copy button in its default place.

```{.python}
import polars as pl
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { expect, Locator, test } from "@playwright/test";
import { getUrl } from "../src/utils";

// Regression test for #13444. The code copy button sits in the top-right
// corner of the code block, where the annotation gutter shows the marker of
// an annotated first line. The button must stay clear of the gutter so the
// marker can still be hovered (or clicked) to show its annotation.

const rightEdge = async (locator: Locator) => {
const box = await locator.boundingBox();
expect(box).not.toBeNull();
return box!.x + box!.width;
};

const testCases = [
{ name: "hover", url: "html/code-annotation-copy-button.html" },
{ name: "select", url: "html/code-annotation-copy-button-select.html" },
{
name: "hover with a custom code block font size",
url: "html/code-annotation-copy-button-font-size.html",
},
{
name: "hover with a code block font size in percent",
url: "html/code-annotation-copy-button-font-size-percent.html",
},
];

test.describe("code copy button and code annotation marker", () => {
for (const { name, url } of testCases) {
test(`${name} — copy button does not cover the marker`, async ({ page }) => {
await page.goto(getUrl(url), { waitUntil: "load" });

const scaffold = page.locator(".code-copy-outer-scaffold").filter({
has: page.locator("pre.code-annotated"),
});
const anchor = scaffold.locator("button.code-annotation-anchor");
await expect(anchor).toHaveCount(1);
const box = await anchor.boundingBox();
expect(box).not.toBeNull();
const x = box!.x + box!.width / 2;
const y = box!.y + box!.height / 2;

// Hovering the code block reveals the copy button, so check the marker
// is still the top-most element at its own center while the button is
// shown.
await page.mouse.move(x, y);
const hit = await page.evaluate(
([px, py]) =>
document.elementFromPoint(px, py)?.closest(
".code-annotation-anchor, .code-copy-button",
)?.className ?? null,
[x, y],
);
expect(hit).toContain("code-annotation-anchor");

// The button sits the default 5px left of the gutter, whatever the code
// block font size the gutter width is derived from.
const gutterLeft = (await scaffold.locator(".code-annotation-gutter")
.boundingBox())!.x;
const buttonRight = await rightEdge(
scaffold.locator(".code-copy-button"),
);
expect(Math.abs(gutterLeft - buttonRight - 5)).toBeLessThanOrEqual(1);

if (name === "select") {
await page.mouse.click(x, y);
}
await expect(page.locator(".code-annotation-tip-content")).toBeVisible();
});
}

test("copy button of a block without annotations is unchanged", async ({
page,
}) => {
await page.goto(getUrl("html/code-annotation-copy-button.html"), {
waitUntil: "load",
});

const scaffold = page.locator(".code-copy-outer-scaffold").filter({
hasNot: page.locator("pre.code-annotated"),
});
await expect(scaffold).toHaveCount(1);
const button = scaffold.locator(".code-copy-button");
await expect(button).toHaveCSS("right", "0px");
await expect(button).toHaveCSS("margin-right", "5px");
expect((await rightEdge(scaffold)) - (await rightEdge(button)))
.toBeCloseTo(5, 0);
});
});
Loading