Capturing the output of a layout template

I need to massage the rendered HTML on a page. The output from tasklists generated by Goldmark is problematic and there’s not a render hook for lists / list items.

Consider baseof.html with

{{- block "main" . }}{{ end }}

And a corresponding single.html with

{{- define "main" }}
		{{ .Content }}
{{- end }}

I want to change baseof.html to something like

{{- $main := (block "main" .) }}
{{- $main = replaceRE `...` "..." $main }}
{{- $main }}

I know it’s not possible to use block in an expression the way you can with partial. I’m curious what other strategies for this are? I’m currently using a partial within my layout template so I can operate on .Content directly, but I need to remember to use the partial in each layout template. Not the biggest deal, but makes me curious about a better approach.

{{- $content := .Content }}
{{- $content = partial "fixup-tasklists.html" .Content }}
{{- $content }}

I am pretty sure I am missing something in your issue. The mentioned badly rendered lists will not come out in your <head> section and not your surrounding markup won’t they? In that case the last three lines where you load .Content and then run them through a partial is exactly what you want to do (apart from a dedicated list-shortcode).

Sure, you need to repeat that for all layouts, but you could require a partial every where you output .Content.

There is the default construct, where you define a block location, and if it’s not defined in the subsequent runs you do something else… that’s probably the place to do something like this:

{{- block "content" . -}}
{{/* untested, the context could be different here */}}
{{- $content := .Content }}
{{- $content = partial "fixup-tasklists.html" .Content }}
{{- $content }}
{{- end -}}

If your layout has a {{ define “content” }} everything between start and end tag will be ignored/overridden.

In what way?

The only templates that can be “captured” is:

  • Partial executions.
  • templates.ExecuteAsTemplate

The main issues is that they can’t be styled without :has() which is relatively new. There’s nothing on the <li> that tells you it’s a task list so you can disable the default list item marker. I’m targeting 5 years of browser support and :has() is only supported among the major browsers for about a year and even less for smaller browsers.

The reasons I want to style it are:

  • The alignment is wrong and doesn’t match other list types
  • Disabled inputs look bad with my color scheme on some browsers
  • Chrome has an accessibility callout for <input> elements without names or ids

Even GitHub has to massage the HTML output to be able to style task lists properly.

A few screenshots here: Annotate <li> when it's a tasklist item · Issue #486 · yuin/goldmark · GitHub

WIth this site configuration:

[markup.goldmark.parser.attribute]
block = true
title = true

And this Markdown:

- [x] item 1
- [ ] item 2
  - [ ] item 2.1
  - [x] item 2.2
- [ ] item 3
{.todo}

Hugo renders this HTML:

<ul class="todo">
  <li><input checked="" disabled="" type="checkbox"> item 1</li>
  <li><input disabled="" type="checkbox"> item 2
    <ul>
      <li><input disabled="" type="checkbox"> item 2.1</li>
      <li><input checked="" disabled="" type="checkbox"> item 2.2</li>
    </ul>
  </li>
  <li><input disabled="" type="checkbox"> item 3</li>
</ul>

Then you can target whatever you want:

ul.todo {
  padding: 0;
}

ul.todo li {
  list-style: none;
}

ul.todo li input {
  width: 1.5em;
  height: 1.5em;
  vertical-align: middle;
}

ul.todo li input:checked {
  /* something */
}
1 Like

Looking at the issue you created in the yuin/goldmark repository, you also noted accessibility concerns. You can handle this with JS, something like:

const els = document.querySelectorAll('ul.todo li input');
els.forEach(el => {
  el.setAttribute('aria-label', 'Is task complete?');
});

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.