Repository navigation
Fix revealjs and epub callout id rendered on two divs - #15041
Merged
Merged
Conversation
Collaborator
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
The revealjs/epub callout renderer wraps the `.callout` div in an outer div when the callout has leftover classes or attributes, so that user classes such as `.fragment` survive (#5208, #6853). The wrapper was also created whenever the callout had an id, and it was given the whole attribute set, identifier included. Since xref callouts (v1.4.458) the inner `.callout` div carries the id too, so every callout with an id emitted it twice. Duplicate ids are invalid HTML: `getElementById()` returns the wrapper, and a CSS `#id` rule applies to both divs. Keep the id on the `.callout` div only, as in HTML output, so `.callout#x` selectors match in every format. The wrapper is now created only when classes or attributes are left over, and it gets a copy of the attributes without the identifier. An id-only callout renders as a single div. Fixes #15036
cderv
force-pushed
the
fix/revealjs-callout-duplicate-id
branch
from
October 14, 2026 09:22
8423544 to
a614070
Compare
3 of 5 tasks
cderv
added a commit
that referenced
this pull request
Oct 14, 2026
A callout's title option was also written out as an HTML title attribute: on the .callout div in html and on the wrapper div in revealjs and epub. Browsers showed it as a tooltip over the whole callout, repeating the heading. The callout parser read title without removing it from the div attributes, unlike appearance, collapse and icon, so it was copied with the other leftover attributes. - Remove title from the div attributes once the parser has read it - Titled revealjs and epub callouts with only an id no longer get a wrapper div; other attributes such as data-* still pass through Related to #15041
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In revealjs and EPUB output, a callout with an id renders that id on two nested divs:
Duplicate ids are invalid HTML.
getElementById()returns only the outer div, and a CSS#my-idrule applies to both (doubled borders, margins, padding). HTML output is not affected: the bootstrap renderer emits a single.calloutdiv.Root cause
The revealjs/epub renderer wraps the
.calloutdiv in an outer div so leftover classes such as.fragmentsurvive (#5208, #6853). The wrapper was created whenever the callout had an id, and it received the full attribute set, identifier included. When cross-referenceable callouts were added (a0a7b4a, first released in v1.4.458), the inner.calloutdiv started carrying the id as well, so both divs have it. Reproduced on released 1.4.549 and v1.9.38, and on current main.Fix
The id stays on the
.calloutdiv, the same element as in HTML output, so.callout#xselectors behave the same across formats. The wrapper is now created only when classes or attributes are left over, and it gets a copy of the attributes without the identifier. An id-only callout renders as a single div.Alternative considered: keeping the id on the wrapper and blanking the inner one. This was rejected because the id would no longer be on the
.calloutelement, and.callout#x(used in the existingrevealjs-callouts.spec.tslocators) would stop matching.This changes what
#xselects in revealjs and EPUB, so it targets 1.11 without a backport. Selectors such as#x > .calloutorgetElementById('x').querySelector('.callout')no longer match, and neither works in HTML. Reveal fragment-event code keyed onevent.fragment.idfor{#x .callout-note .fragment}is affected too, since.fragmentstays on the wrapper.The
titleattribute still leaks onto the wrapper as an HTML attribute, which keeps titled callouts wrapped. That is separate from this fix.Test plan
div.callout#…selectors and absence of[id=x] [id=x]in the new smoke-all document, reverse-applying the fix gives an assertion failure6853.qmd(fragment wrapper) still passes.epubandgrep -rhn 'id="my-id\|id="other' EPUB/text/shows one line per id, each onclass="callout …"Fixes #15036
Checklist
I have (if applicable):
AI-assisted PR
Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.