Skip to main content

ejs-listing-port

Port a Quarto 1 EJS custom listing template to a Quarto 2 doctemplate, or write a new one. Use when a render warns Q-12-7 ("template: was set but type: is not custom"), Q-12-9 (".ejs" / ".ejs.md" extension), Q-12-24 ("template contains no doctemplate directives"), or Q-12-10 ("Undefined variable"); when a listing renders with the built-in layout instead of the custom one, or dumps the template verbatim into the page; when listing links point at .qmd files or listing images 404; or when writing any custom listing template for Quarto 2.

Informações da origem

Repositório
quarto-dev/q2
Última atividade na origem
26 de agosto de 2026 às 19:16
Idioma detectado do SKILL.md
inglês
Estrelas
268
Forks
17

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
2 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
ejs-listing-port
description
Port a Quarto 1 EJS custom listing template to a Quarto 2 doctemplate, or write a new one. Use when a render warns Q-12-7 ("template: was set but type: is not custom"), Q-12-9 (".ejs" / ".ejs.md" extension), Q-12-24 ("template contains no doctemplate directives"), or Q-12-10 ("Undefined variable"); when a listing renders with the built-in layout instead of the custom one, or dumps the template verbatim into the page; when listing links point at .qmd files or listing images 404; or when writing any custom listing template for Quarto 2.
# ejs-listing-port Skill Quarto 1 custom listing templates were EJS — embedded JavaScript. Quarto 2 does not run EJS: custom listing templates are **doctemplates**, the `$variable$` / `$if(…)$` / `$for(…)$` syntax Pandoc templates use, so a template can never execute code. The syntax translation is mechanical and self-announcing — a half-finished port either warns or looks obviously wrong. **The two things that make ports fail are not syntax**, and both are silent: no error, no warning, and no visible difference in the template's text. State them out loud before you start, or you will skip them. ## The two silent failure modes ### 1. Links and images must be markdown, never raw HTML A template's output is markdown, re-parsed into the page. Only *then* are paths resolved: `LinkRewriteTransform` rewrites `.qmd` link targets to output URLs, and resource collection notes every image so the file gets copied. Both walk **parsed markdown nodes**. `Block::RawBlock` and `Inline::RawInline` are no-op leaves in both (`crates/quarto-core/src/transforms/link_rewrite.rs`, `.../resource_collector.rs`), so a path inside a raw-HTML attribute is invisible to each. `$it.path$` is deliberately a *source* path, which is what makes the markdown form work: ``` [$it.title$]($it.path$) ✔ becomes href="…​.html" `<a href="$it.path$">`{=html} ✘ ships a dead .qmd href ``` For images the same split costs **two** things — rewriting *and* copying: ``` ![$it.image-alt$]($it.image$) ✔ rewritten, and the file is copied `<img src="$it.image$">`{=html} ✘ neither: the src 404s in the deployed site ``` Raw HTML is fine *inside* the link text — the built-ins do exactly this with ``[`$image-html$`{=html}]($path$)``. **Anchor markdown, contents raw.** This is settled design, not a gap: Quarto 2 emits HTML from the AST and will not parse HTML you author (`claude-notes/plans/2026-04-24-websites-phase-6.md` Decision 1; `claude-notes/plans/2026-08-13-site-root-relative-paths.md` Case C). Do not propose an HTML post-processor. Make the markdown path obvious instead. **Why this survives review.** The image failure hides itself whenever the image is an item *document's* own front-matter `image:` — that page's render copies the file regardless, so a raw-`<img>` template looks perfect. It only bites for inline-record fields and custom fields, which is exactly where a ported gallery or card grid gets its thumbnails. ### 2. The description placeholder envelope must be unconditional Quarto 1 auto-filled a missing item description from the first paragraph of the rendered item page. Quarto 2 does the same, via a post-render substitution — but only inside the `description-placeholder-begin` / `-end` markers, and only if they are emitted **outside** the `$if$`: ```` ::: {.listing-description} ```{=html} $it.description-placeholder-begin$ ``` $if(it.description)$ $it.description$ $endif$ ```{=html} $it.description-placeholder-end$ ``` ::: ```` The markers delimit the *region* to substitute, so they must exist for exactly the items the `$if$` skips. The built-in templates gate the whole envelope on `$if(description)$` — so **copying the built-in shape loses previews for precisely the items that need them.** A custom template can and should do better. ## Read this first `docs/guides/projects/listing-templates.qmd` in the q2 repo is the authoritative treatment: both rules above, the doctemplate language subset, what each built-in layout emits, the Q1 → Q2 mapping table, a full worked before/after port, and what doctemplates cannot do. Its sibling `docs/guides/projects/listings.qmd` (§"Custom templates") carries the syntax table and the per-item value list. Canonical spellings, since Q1 habits produce the wrong ones: - `$it.<key>$` inside `$for(items)$`. (`$items.<key>$` is an accepted alias; prefer `$it$`.) Inside a partial applied to an item, keys are bare: `$title$`. - `type: custom` **and** `template:` are both required. `template:` alone warns `Q-12-7` and silently uses the built-in layout. - Give the file a neutral extension (`.template`) so `Q-12-9` stops firing. ## Worked examples - **The built-ins** — `crates/quarto-core/src/project/listing/templates/` (`item-default`, `item-grid`, `item-table`, and the `listing-*` wrappers). Short, idiomatic, and the reference for class names a custom template must match to inherit the listing CSS. - **`references/worked-examples.md`** in this skill — three annotated ports in increasing difficulty, including the phrasing-content constraint that bites card grids. - **The in-repo contract tests** — `crates/quarto-core/tests/integration/listing_pipeline.rs`, the three `custom_template_*` tests. They pin both silent failure modes and the unconditional envelope, so they are executable documentation of the rules above. ## Procedure 1. **Read the Q1 template and inventory what it does** beyond interpolation: helper calls, JS prologues, expressions, nested loops, grouping. Those need decisions, not translation — see "What doctemplates cannot do" in the guide (JS constants → `template-params:`; string manipulation → pre-computed record key or `listing-item.extra`). 2. **Drop the outer `{=html}` fence.** A Q1 listing template is usually one big raw-HTML block; that fence is what makes every path inside it invisible. Removing it is most of the port. 3. **Convert `<div class="…">` to `::: {.…}`.** Same output element, but the contents are markdown, so links and images inside resolve. 4. **Convert every anchor and image to markdown** (rule 1). Keep raw HTML only as link *text* or as genuinely inert markup. 5. **Add the description envelope unconditionally** (rule 2) if the listing wants previews. 6. **Guard every optional read with `$if$`** — an absent value warns `Q-12-10` rather than rendering blank. 7. **Verify against the rendered output** (below). Do not stop at "it renders." ## Verification — required, and not optional Neither silent failure produces a diagnostic, and neither changes the template's text in a way review catches. A passing render proves nothing. After porting, render the project and inspect the **output**: ```bash cargo run --bin q2 -- render <project> # in q2; `quarto render .` for users ``` 1. **Read the `href` values** in the listing's host page. Every link to a project document must end in `.html`. A surviving `.qmd` means that anchor is still raw HTML. 2. **Read the `src` values, then confirm each file exists** under the output directory. A `src` that looks right with no file behind it is a raw `<img>` — this is the check that catches the masked case, and the one people skip. 3. **Check an item with no front-matter `description:`.** It should show a first-paragraph preview. Nothing shown means the envelope is inside the `$if$` instead of around it. 4. **Grep the render output for `Q-12-`.** Any warning here is real. Report what you inspected, not just that the render succeeded. If you could not check one of the four, say which.
Ver no GitHub