Разработка кастомного шорткода Hugo
Шорткоды Hugo — механизм встраивания переиспользуемых компонентов непосредственно в Markdown-контент. Это решение для случаев, когда нужно добавить в статью что-то сложнее обычного текста: предупреждение, таблицу сравнения, вставку видео с параметрами, интерактивный блок. Авторы контента не трогают HTML — они используют простой синтаксис.
Синтаксис вызова
{{</* предупреждение type="warning" */>}}
Текст предупреждения
{{</* /предупреждение */>}}
{{</* youtube id="dQw4w9WgXcQ" autoplay="false" */>}}
{{</* цитата author="Дональд Кнут" source="The Art of Computer Programming" */>}}
Преждевременная оптимизация — корень всех зол.
{{</* /цитата */>}}
Два варианта синтаксиса:
-
{{< >}}— вывод без экранирования HTML (для компонентов с разметкой) -
{{% %}}— контент внутри обрабатывается как Markdown
Простой шорткод: callout/предупреждение
{{/* layouts/shortcodes/callout.html */}}
{{ $type := .Get "type" | default "info" }}
{{ $title := .Get "title" }}
{{ $icons := dict
"info" "ℹ️"
"warning" "⚠️"
"danger" "🚨"
"success" "✅"
"tip" "💡"
}}
<div class="callout callout--{{ $type }}">
<div class="callout__icon">{{ index $icons $type }}</div>
<div class="callout__body">
{{ with $title }}
<strong class="callout__title">{{ . }}</strong>
{{ end }}
<div class="callout__content">{{ .Inner | markdownify }}</div>
</div>
</div>
Использование:
{{</* callout type="warning" title="Важно" */>}}
Перед обновлением сделайте резервную копию базы данных.
{{</* /callout */>}}
Шорткод с позиционными параметрами
{{/* layouts/shortcodes/figure.html */}}
{{ $src := .Get 0 }}
{{ $alt := .Get 1 | default "" }}
{{ $caption := .Get 2 | default "" }}
{{ $width := .Get "width" | default "100%" }}
{{ $img := resources.Get $src }}
{{ if $img }}
{{ $webp := $img | images.Resize (printf "%s WebP" (default "1200x" (.Get "resize"))) }}
<figure class="article-figure" style="max-width: {{ $width }}">
<picture>
<source srcset="{{ $webp.Permalink }}" type="image/webp">
<img src="{{ $img.Permalink }}" alt="{{ $alt }}" loading="lazy">
</picture>
{{ with $caption }}
<figcaption>{{ . | markdownify }}</figcaption>
{{ end }}
</figure>
{{ else }}
<!-- Fallback для внешних изображений -->
<figure class="article-figure" style="max-width: {{ $width }}">
<img src="{{ $src }}" alt="{{ $alt }}" loading="lazy">
{{ with $caption }}<figcaption>{{ . }}</figcaption>{{ end }}
</figure>
{{ end }}
Использование:
{{</* figure "images/architecture.png" "Архитектура системы" "Диаграмма компонентов приложения" width="80%" */>}}
Шорткод с вкладками (tabs)
Сложный случай: несколько шорткодов, связанных между собой через scratch:
{{/* layouts/shortcodes/tabs.html */}}
{{ .Scratch.Set "tabs" slice }}
{{ .Inner | markdownify }}
{{ $tabs := .Scratch.Get "tabs" }}
<div class="tabs" data-tabs>
<div class="tabs__nav" role="tablist">
{{ range $i, $tab := $tabs }}
<button
class="tabs__trigger{{ if eq $i 0 }} is-active{{ end }}"
role="tab"
aria-selected="{{ if eq $i 0 }}true{{ else }}false{{ end }}"
aria-controls="tab-panel-{{ $tab.id }}"
>{{ $tab.label }}</button>
{{ end }}
</div>
{{ range $i, $tab := $tabs }}
<div
id="tab-panel-{{ $tab.id }}"
class="tabs__panel{{ if not (eq $i 0) }} is-hidden{{ end }}"
role="tabpanel"
>{{ $tab.content | safeHTML }}</div>
{{ end }}
</div>
{{/* layouts/shortcodes/tab.html */}}
{{ $label := .Get "label" }}
{{ $id := $label | urlize }}
{{ $content := .Inner | markdownify }}
{{ $tabs := .Parent.Scratch.Get "tabs" }}
{{ $tabs = $tabs | append (dict "label" $label "id" $id "content" $content) }}
{{ .Parent.Scratch.Set "tabs" $tabs }}
JavaScript для переключения:
document.querySelectorAll('[data-tabs]').forEach(tabGroup => {
tabGroup.querySelectorAll('.tabs__trigger').forEach((trigger, i) => {
trigger.addEventListener('click', () => {
tabGroup.querySelectorAll('.tabs__trigger').forEach(t => {
t.classList.remove('is-active');
t.setAttribute('aria-selected', 'false');
});
tabGroup.querySelectorAll('.tabs__panel').forEach(p => p.classList.add('is-hidden'));
trigger.classList.add('is-active');
trigger.setAttribute('aria-selected', 'true');
tabGroup.querySelector(`#${trigger.getAttribute('aria-controls')}`).classList.remove('is-hidden');
});
});
});
Шорткод для вставки кода с заголовком
{{/* layouts/shortcodes/code.html */}}
{{ $lang := .Get "lang" | default "bash" }}
{{ $title := .Get "title" | default "" }}
{{ $filename := .Get "filename" | default "" }}
<div class="code-block">
{{ if or $title $filename }}
<div class="code-block__header">
{{ with $filename }}<span class="code-block__filename">{{ . }}</span>{{ end }}
{{ with $title }}<span class="code-block__title">{{ . }}</span>{{ end }}
<button class="code-block__copy" onclick="copyCode(this)">Копировать</button>
</div>
{{ end }}
<div class="code-block__content">
{{ highlight (.Inner | trim "\n") $lang "" }}
</div>
</div>
Шорткод для таблицы сравнения
{{/* layouts/shortcodes/compare.html */}}
{{ $headers := split (.Get "headers") "," }}
{{ $rows := split .Inner "\n" | after 0 }}
<div class="compare-table-wrapper">
<table class="compare-table">
<thead>
<tr>
{{ range $headers }}
<th>{{ . | trim " " }}</th>
{{ end }}
</tr>
</thead>
<tbody>
{{ range $rows }}
{{ if . }}
<tr>
{{ $cells := split . "|" }}
{{ range $cells }}
<td>{{ . | trim " " | markdownify }}</td>
{{ end }}
</tr>
{{ end }}
{{ end }}
</tbody>
</table>
</div>
Дебаггинг шорткодов
{{/* Вывод всех параметров для отладки */}}
{{ if hugo.IsServer }}
<pre class="debug-shortcode">
Params: {{ .Params | jsonify (dict "indent" " ") }}
Inner: {{ .Inner }}
Parent: {{ with .Parent }}{{ .Name }}{{ end }}
</pre>
{{ end }}
Сроки
Один простой шорткод (предупреждение, цитата, badge) — 2–4 часа. Набор из 5–10 шорткодов с CSS и базовым JS — 2–3 дня. Сложные связанные шорткоды (tabs, accordion, compare table) с полным покрытием edge-кейсов — 3–5 дней.







