|
| 1 | +--- |
| 2 | +main_commit: 98be8a667 |
| 3 | +analyzed_date: 2026-02-23 |
| 4 | +key_files: |
| 5 | + - src/format/reveal/format-reveal.ts |
| 6 | + - src/format/reveal/constants.ts |
| 7 | + - src/resources/formats/revealjs/pandoc/template.html |
| 8 | + - src/resources/formats/revealjs/pandoc/revealjs.template |
| 9 | +--- |
| 10 | + |
| 11 | +# Revealjs Format Architecture |
| 12 | + |
| 13 | +How Quarto configures reveal.js presentations, covering metadata handling, template rendering, and the division of responsibilities between TypeScript and Pandoc templates. |
| 14 | + |
| 15 | +## Key Files |
| 16 | + |
| 17 | +| File | Role | |
| 18 | +|------|------| |
| 19 | +| `src/format/reveal/format-reveal.ts` | Format definition, metadata normalization, extras | |
| 20 | +| `src/format/reveal/constants.ts` | Metadata key constants | |
| 21 | +| `src/resources/formats/revealjs/pandoc/template.html` | Quarto's active Pandoc template | |
| 22 | +| `src/resources/formats/revealjs/pandoc/revealjs.template` | Reference copy of Pandoc's upstream template | |
| 23 | + |
| 24 | +## Two Template Files |
| 25 | + |
| 26 | +Quarto maintains two revealjs template files. They serve different purposes: |
| 27 | + |
| 28 | +**`revealjs.template`** is a direct copy of Pandoc's `default.revealjs`, auto-generated by `package/src/common/update-pandoc.ts` during Pandoc version updates. Do not modify this file — changes will be overwritten on the next Pandoc update. |
| 29 | + |
| 30 | +**`template.html`** is Quarto's active template, specified in `formatExtras()` via `templateContext`. It diverges from the upstream template where Quarto needs different behavior (type-safe rendering, additional features, etc.). |
| 31 | + |
| 32 | +## Metadata Handling Pattern |
| 33 | + |
| 34 | +Revealjs configuration flows through three stages with distinct responsibilities: |
| 35 | + |
| 36 | +### Stage 1: Normalization — `revealResolveFormat()` |
| 37 | + |
| 38 | +Maps user-facing YAML keys to reveal.js configuration keys. Runs early in format resolution. |
| 39 | + |
| 40 | +Responsibilities: |
| 41 | +- Map compound YAML structures to flat metadata (e.g., `scroll-view.snap` → `scrollSnap`) |
| 42 | +- Normalize values (e.g., `navigationMode: "vertical"` → `"default"`) |
| 43 | +- Create helper flags for template type handling (e.g., `scrollProgressAuto`) |
| 44 | +- Remove intermediate metadata keys (e.g., delete `scroll-view` after extracting sub-options) |
| 45 | + |
| 46 | +Does NOT set defaults — only transforms what the user provided. |
| 47 | + |
| 48 | +### Stage 2: Defaults — `extras.metadata` in `formatExtras()` |
| 49 | + |
| 50 | +Sets opinionated default values that can be overridden by user metadata. These are the lowest priority in the metadata chain. |
| 51 | + |
| 52 | +```typescript |
| 53 | +// General revealjs defaults |
| 54 | +extras.metadata = { |
| 55 | + ...extras.metadata, |
| 56 | + ...revealMetadataFilter({ |
| 57 | + width: 1050, |
| 58 | + height: 700, |
| 59 | + center: false, |
| 60 | + transition: "none", |
| 61 | + // ... |
| 62 | + }), |
| 63 | +}; |
| 64 | + |
| 65 | +// Conditional defaults (e.g., scroll-view options when view is "scroll") |
| 66 | +if (format.metadata[kView] === "scroll") { |
| 67 | + extras.metadata = { |
| 68 | + ...extras.metadata, |
| 69 | + [kScrollSnap]: "mandatory", |
| 70 | + [kScrollLayout]: "full", |
| 71 | + [kScrollActivationWidth]: 0, |
| 72 | + }; |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +Setting defaults explicitly (rather than relying on Pandoc's `defField`) ensures the template always has values to render. |
| 77 | + |
| 78 | +### Stage 3: Post-processing — `fixupRevealJsInitialization()` |
| 79 | + |
| 80 | +DOM manipulation of the rendered HTML, handling values that can't be fixed in the template: |
| 81 | + |
| 82 | +- Quoting `slideNumber` string values (e.g., `h.v` → `'h.v'`) |
| 83 | +- Quoting percentage-based `width`/`height` values |
| 84 | +- Injecting `extraConfig` values (options not in the template) |
| 85 | +- Registering plugins |
| 86 | + |
| 87 | +Use this stage only when template-level handling isn't possible. |
| 88 | + |
| 89 | +### Metadata Priority (highest to lowest) |
| 90 | + |
| 91 | +1. `metadataOverride` — forces values regardless of user settings |
| 92 | +2. `format.metadata` — user values + normalization from `revealResolveFormat()` |
| 93 | +3. Pandoc `defField` — Pandoc writer defaults for unset variables |
| 94 | +4. `extras.metadata` — Quarto's opinionated defaults |
| 95 | + |
| 96 | +## Template Type Handling |
| 97 | + |
| 98 | +Pandoc templates render values as text. This creates type mismatches when reveal.js expects specific JavaScript types. Quarto's `template.html` uses conditional guards to render correct JS types. |
| 99 | + |
| 100 | +### The Problem |
| 101 | + |
| 102 | +Pandoc template variables have limited type awareness: |
| 103 | +- `$var$` renders `BoolVal True` as `true`, `BoolVal False` as `false` (correct for JS booleans) |
| 104 | +- `'$var$'` always renders as a quoted string, even for `false` → `'false'` (wrong — truthy in JS) |
| 105 | +- Numbers rendered inside quotes become strings: `'$var$'` with `0` → `'0'` (wrong if JS expects a number) |
| 106 | + |
| 107 | +### Pattern: Mixed-Type Options |
| 108 | + |
| 109 | +When an option accepts both strings and booleans (e.g., `scrollSnap: "mandatory" | "proximity" | false`): |
| 110 | + |
| 111 | +``` |
| 112 | +$if(scrollSnap)$ |
| 113 | + scrollSnap: '$scrollSnap/nowrap$', |
| 114 | +$else$ |
| 115 | + scrollSnap: false, |
| 116 | +$endif$ |
| 117 | +``` |
| 118 | + |
| 119 | +This works because Pandoc's `$if()$` evaluates `BoolVal False` as false, so: |
| 120 | +- String values (`"mandatory"`, `"proximity"`) → `$if$` is true → quoted output |
| 121 | +- Boolean `false` → `$if$` is false → `$else$` renders unquoted `false` |
| 122 | + |
| 123 | +### Pattern: String "auto" with Boolean Fallback |
| 124 | + |
| 125 | +When an option accepts `"auto" | true | false` (e.g., `scrollProgress`), a helper flag avoids rendering `"auto"` as a boolean: |
| 126 | + |
| 127 | +TypeScript (normalization stage): |
| 128 | +```typescript |
| 129 | +if (value === "auto" || value === undefined) { |
| 130 | + format.metadata[kHelperFlag] = true; |
| 131 | + delete format.metadata[kOriginalKey]; |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +Template: |
| 136 | +``` |
| 137 | +$if(helperFlag)$ |
| 138 | + option: 'auto', |
| 139 | +$elseif(option)$ |
| 140 | + option: $option$, |
| 141 | +$else$ |
| 142 | + option: false, |
| 143 | +$endif$ |
| 144 | +``` |
| 145 | + |
| 146 | +### Pattern: Numeric Options |
| 147 | + |
| 148 | +When reveal.js checks `typeof value === 'number'`, the template must NOT quote the value: |
| 149 | + |
| 150 | +``` |
| 151 | + scrollActivationWidth: $scrollActivationWidth$, |
| 152 | +``` |
| 153 | + |
| 154 | +Not `'$scrollActivationWidth$'` — that renders as string `'0'` instead of number `0`. |
| 155 | + |
| 156 | +## Known Pandoc Template Issues |
| 157 | + |
| 158 | +Pandoc's upstream `revealjs.template` (copied to `revealjs.template`) has type issues in the scroll-view block that Quarto's `template.html` fixes. Tracked in [jgm/pandoc#11486](https://github.com/jgm/pandoc/issues/11486): |
| 159 | + |
| 160 | +- `scrollSnap: '$scrollSnap$'` renders `false` as string `'false'` (truthy in JS) |
| 161 | +- `scrollActivationWidth: '$scrollActivationWidth$'` renders numbers as strings |
| 162 | +- `scrollProgress` defField defaults to `true` instead of reveal.js's `'auto'` |
| 163 | + |
| 164 | +## Adding New Reveal.js Options |
| 165 | + |
| 166 | +When adding support for a new reveal.js configuration option: |
| 167 | + |
| 168 | +1. Add the constant to `constants.ts` |
| 169 | +2. If the option needs YAML normalization (e.g., a compound structure), add to `revealResolveFormat()` |
| 170 | +3. If the option needs a default value, add to `extras.metadata` in `formatExtras()` |
| 171 | +4. If the option needs type-safe rendering, add to `template.html` with appropriate `$if/$else$` guards |
| 172 | +5. If the option can only be handled post-render, add to `extraConfig` (last resort) |
| 173 | +6. Add smoke-all tests covering type edge cases |
0 commit comments