Assuming that markup.goldmark.extensions.definitionList = true (the default)…
This markdown:
term
: definition
is rendered to:
<dl>
<dt>term</dt>
<dd>definition</dd>
</dl>
but what I really, really want is:
<dl>
<dt id="term">term</dt>
<dd>definition</dd>
</dl>
where the id attribute is automatically generated and, if necessary, incremented to prevent (or least mitigate) duplicate id’s on the page (the same way that we handle heading id attributes).
While you can apply a markdown attribute to the description list, you cannot apply a markdown attribute to the term or definition.
You can obviously work around this by using heading elements instead of a description list, but (a) the description list seems a semantically better choice, and (b) the additional heading elements may pollute the TOC depending on configuration (endLevel).
A couple of examples from our docs:
Description list: https://gohugo.io/functions/highlight/#options
Heading elements: https://gohugo.io/getting-started/configuration/#all-configuration-settings
I am guessing that we used heading elements in the second example in order to provide link targets.
If you’re willing to be a bit unconventional…
markdown
[term](@)
: definition
layouts/_default/_markup/render-link.html
{{- if eq .Destination "@" -}}
<span id="{{ .PlainText | anchorize }}">{{ .Text | safeHTML }}</span>
{{- else -}}
...
{{- end -}}
renders to:
<dl>
<dt><span id="term">term</span></dt>
<dd>definition</dd>
</dl>
bep
April 14, 2022, 5:58pm
3
No, the prime motivation for that is the Algolia search indexer. I have gotten access to the Algolia account that does this now, and I have on my list to look at some other search related issues (mostly the case of double-indexing pages).
I’m not sure I follow your logic, though; what do you need those IDs for? It’s not like we include these dt elements in the TOC?
For example, let’s say I want to link to the “lineNoStart” dt element on https://gohugo.io/functions/highlight .
bep
April 14, 2022, 6:13pm
5
Yes, that I understand. OK, so you mean manual linking, then. Yea, that would be useful…
Since there is no render hook for dt elements I tried a different approach and it works perfectly fine for me.
Replace the {{ .Content }} part of the layout with
{{ partial "plugins/definition-term-ids.html" .Content | safeHTML }}
This passes the already rendered HTML content to the partial definition-term-ids.
The partial in layouts/partials/plugins/definition-term-ids.html is built like this:
{{- $content := . -}}
{{- $dtPattern := `(?s)<dt(?:\s+[^>]*)?>.*?</dt>` -}}
{{- $seenIDs := newScratch -}}
{{- range findRE $dtPattern $content -}}
{{- $match := . -}}
{{- if not (findRE `(?i)^<dt[^>]*\sid=` $match) -}}
{{- $openTag := replaceRE `(?s)^(<dt(?:\s+[^>]*)?>).*` "$1" $match -}}
{{- $termHTML := replaceRE `(?s)^<dt(?:\s+[^>]*)?>(.*?)</dt>$` "$1" $match -}}
{{- $termText := $termHTML | plainify | strings.TrimSpace -}}
{{- $baseID := $termText | anchorize -}}
{{- if $baseID -}}
{{- $count := add (default 0 ($seenIDs.Get $baseID)) 1 -}}
{{- $seenIDs.Set $baseID $count -}}
{{- $termID := $baseID -}}
{{- if gt $count 1 -}}
{{- $termID = printf "%s-%d" $baseID $count -}}
{{- end -}}
{{- $updatedOpenTag := replace $openTag "<dt" (printf `<dt id="%s"` $termID) 1 -}}
{{- $replacement := replace $match $openTag $updatedOpenTag 1 -}}
{{- $content = replace $content $match $replacement 1 -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- return $content -}}
These are the important steps:
range over all elements that match the $dtPattern
if element already has an id attribute, skip it
extract the term, plainly (remove additional inner markup) and anchorize (create URL compatible text) it
check if id value was already seen and increment if necessary
replace the opening tag with the tag that includes the id attribute
return content
I’m not sure I understand the above, but if you just want to add a unique id attribute to each Mardown dt element…
[markup.goldmark.parser]
autoDefinitionTermID = true
This feature was added last year in v0.144.0 .
Oh wow. Thank you! That’s obviously the better solution and does exactly what I needed. I totally missed this.
Yeah, it works great. We use it extensively on the Hugo documentation site. For example:
https://gohugo.io/configuration/all/#locale