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 @@ -51,6 +51,7 @@ All changes included in 1.11:

- ([#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.
- ([#15007](https://github.com/quarto-dev/quarto-cli/pull/15007)): Fix Home/End on a focused tabset tab also jumping to the first or last slide in addition to switching tabs.
- ([#15014](https://github.com/quarto-dev/quarto-cli/issues/15014)): Fix code blocks at the end of a callout losing their bottom spacing, which made the code block border touch the callout border in untitled `appearance="simple"` callouts (regression introduced in v1.8.15).

### `typst`

Expand Down
10 changes: 6 additions & 4 deletions src/resources/formats/revealjs/quarto.scss
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@ kbd {
> :last-child {
margin-bottom: var(--r-block-margin);
}
> :last-child:not(div.sourceCode) {
> :last-child:not(div.sourceCode, div.code-copy-outer-scaffold) {
padding-bottom: 0.5rem;
margin-bottom: 0;
}
Expand Down Expand Up @@ -970,9 +970,11 @@ kbd {
}
.callout-body {
> .callout-content {
// Margin needs to be added when last child is div.sourceCode
// Other code cell border is mixed with callout border
> div.sourceCode:last-child {
// Margin needs to be added when the last child is a code block
// (div.sourceCode, or the code-copy scaffold wrapping it).
// Otherwise the code block border merges with the callout border.
> div.sourceCode:last-child,
> div.code-copy-outer-scaffold:last-child {
margin-bottom: 1rem;
}
> :first-child {
Expand Down
54 changes: 54 additions & 0 deletions tests/docs/playwright/revealjs/callouts/callout-code-last.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: callout ending with a code block
format: revealjs
---

## Default titled, ends with code

::: {#default-titled-code .callout-note title="Titled"}
Some text before the code.

```python
1 + 1
```
:::

## Simple untitled, ends with code

::: {#simple-untitled-code .callout-note appearance="simple"}
Some text before the code.

```python
1 + 1
```
:::

## Simple titled, ends with code

::: {#simple-titled-code .callout-note appearance="simple" title="Titled"}
Some text before the code.

```python
1 + 1
```
:::

## Default titled, ends with paragraph

::: {#default-titled-paragraph .callout-note title="Titled"}
```python
1 + 1
```

Some text after the code.
:::

## Simple untitled, ends with paragraph

::: {#simple-untitled-paragraph .callout-note appearance="simple"}
```python
1 + 1
```

Some text after the code.
:::
90 changes: 90 additions & 0 deletions tests/integration/playwright/tests/revealjs-callouts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { test, expect } from '@playwright/test';
import { expectCloseTo } from '../src/utils';

// Space below the last block of a callout, in CSS px, for callouts whose last
// block is a code block (wrapped in the code-copy scaffold) or a paragraph.
// Reveal scales the slide, so the measured gap is divided by Reveal.getScale()
// and the callout's own bottom border is subtracted.
// https://github.com/quarto-dev/quarto-cli/issues/15014
type ExpectedGap = '--r-block-margin' | '1rem' | 0;

interface CalloutCase {
name: string;
slide: string;
callout: string;
lastBlock: string;
expectedGap: ExpectedGap;
}

const testCases: CalloutCase[] = [
{
name: 'default titled callout ending with code',
slide: 'default-titled-ends-with-code',
callout: 'default-titled-code',
lastBlock: 'div.sourceCode',
expectedGap: '--r-block-margin',
},
{
name: 'simple untitled callout ending with code',
slide: 'simple-untitled-ends-with-code',
callout: 'simple-untitled-code',
lastBlock: 'div.sourceCode',
expectedGap: '1rem',
},
{
name: 'simple titled callout ending with code',
slide: 'simple-titled-ends-with-code',
callout: 'simple-titled-code',
lastBlock: 'div.sourceCode',
expectedGap: '--r-block-margin',
},
{
name: 'default titled callout ending with paragraph',
slide: 'default-titled-ends-with-paragraph',
callout: 'default-titled-paragraph',
lastBlock: '.callout-content > p:last-child',
expectedGap: 0,
},
{
name: 'simple untitled callout ending with paragraph',
slide: 'simple-untitled-ends-with-paragraph',
callout: 'simple-untitled-paragraph',
lastBlock: '.callout-content > p:last-child',
expectedGap: '--r-block-margin',
},
];

test.describe('Revealjs callout bottom spacing', () => {
for (const { name, slide, callout, lastBlock, expectedGap } of testCases) {
test(`${name} has the expected space below the last block`, async ({ page }) => {
await page.goto(`./revealjs/callouts/callout-code-last.html#/${slide}`);
// Quarto puts the id on a wrapper div and on div.callout; target the one with the border.
const calloutBox = page.locator(`div.callout#${callout}`);
await expect(calloutBox).toBeVisible();
// Reveal adds .ready once layout and scaling are done.
await expect(page.locator('.reveal.ready')).toBeAttached();

const { gap, expected } = await calloutBox.evaluate((el, { lastBlock, expectedGap }) => {
// Resolve a CSS length to px in the page.
const resolve = (value: string) => {
const probe = document.createElement('div');
probe.style.cssText = `position:absolute;visibility:hidden;margin-bottom:${value}`;
document.body.appendChild(probe);
const px = parseFloat(getComputedStyle(probe).marginBottom);
probe.remove();
return px;
};
const block = el.querySelector(lastBlock) as HTMLElement;
const scale = (window as any).Reveal.getScale();
const borderBottom = parseFloat(getComputedStyle(el).borderBottomWidth);
const rawGap = el.getBoundingClientRect().bottom - block.getBoundingClientRect().bottom;
const expected = expectedGap === 0
? 0
: resolve(expectedGap === '1rem' ? '1rem' : `var(${expectedGap})`);
return { gap: rawGap / scale - borderBottom, expected };
}, { lastBlock, expectedGap });

expectCloseTo(gap, expected, 1);
});
}
});
Loading