diff --git a/HUGO_DEPENDENCY_ASSESSMENT.md b/HUGO_DEPENDENCY_ASSESSMENT.md new file mode 100644 index 0000000000..9e6241ae99 --- /dev/null +++ b/HUGO_DEPENDENCY_ASSESSMENT.md @@ -0,0 +1,493 @@ +# Hugo dependency assessment + +Date assessed: 24 July 2026 + +## Executive summary + +The documentation sources are heavily dependent on Hugo today, but most +content-level lock-in is concentrated in a few conventions that can be +replaced without changing the published experience. + +The highest-value changes are: + +1. Replace `relref` calls with ordinary Markdown links and resolve or validate + them with a link render hook. +2. Replace note, warning, tip, and alert shortcodes with Markdown blockquote + alerts and a blockquote render hook. +3. Replace the image shortcode with Markdown images and an image render hook. +4. Replace code-related shortcodes with fenced code blocks. +5. Use table render hooks for responsive Markdown tables. + +These changes would move Hugo-specific behavior out of thousands of content +files and into a small rendering adapter. The equivalent adapter could later +be implemented for another site generator. + +Some shortcodes are not merely presentational. Features such as generated +multi-client examples, content transclusion, child-page tables, and +data-driven command lists require preprocessing or an equivalent data and +page-model API. Render hooks are not an appropriate replacement for these. + +## Scope and methodology + +This assessment covers the current repository checkout and: + +- 5,146 Markdown files under `content/` +- Hugo v0.143.1 +- 41 shortcode templates +- 6 existing render hooks +- 153 layout files + +Shortcode calls and affected files were counted across the Markdown sources. +Front matter, content organization, raw HTML, Hugo configuration, layout +templates, data access, alternate output formats, and build scripts were also +reviewed. + +## Content-level dependency inventory + +| Construct | Instances | Files affected | Assessment | +|---|---:|---:|---| +| `relref` | 26,856 | 3,524 | Largest dependency; readily replaceable | +| Note, warning, tip, info, and alert shortcodes | 2,960 | 1,290 | Readily replaceable with blockquote alerts | +| `image` | 1,648 | 445 | Mostly replaceable with Markdown images | +| `highlight`, `code`, and `redis-cli` | 749 | 340 | Mostly replaceable with fenced code blocks | +| `multitabs` | 624 | 585 | Needs a different progressive-enhancement design | +| `clients-example` | 564 | 109 | Data-generation feature | +| `embed-md` | 299 | 207 | Content transclusion; no standard Markdown equivalent | +| `embed-yaml` | 136 | 32 | Better handled by preprocessing | +| `table-children` | 94 | 89 | Depends on Hugo's page tree | +| Other specialized shortcodes | 409 | — | Mixed presentation and data-generation features | + +Overall: + +- 3,932 files, approximately 76%, contain at least one shortcode, including + `relref`. +- 2,297 files, approximately 45%, contain at least one custom, non-built-in + shortcode. +- Replacing links, callouts, images, and code wrappers would remove about 94% + of all shortcode invocations. +- After those migrations, approximately 1,098 files and 2,126 genuinely custom + shortcode calls would remain. + +## Recommended source conventions + +| Current convention | Preferred source form | Hugo implementation | +|---|---|---| +| `[text]({{< relref "…" >}})` | Ordinary Markdown link | Link render hook resolves and validates it | +| `{{< image … >}}` | `![alt](image-path)` | Image render hook adds lightbox and link behavior | +| `note`, `warning`, `tip`, `info`, `alert` | `> [!NOTE]`, `> [!WARNING]`, etc. | Blockquote render hook produces the existing alert markup | +| `highlight`, `code` | Fenced code block | Default renderer or code-block hook | +| `redis-cli` | A `redis-cli` fenced code block | Code-block hook adds terminal presentation | +| `table-scrollable` | Ordinary Markdown table | Table render hook adds a responsive wrapper | +| `definition` | Markdown definition list | Goldmark definition-list support | +| Diagrams and interactive checklists | Existing typed code fences | Retain the current render-hook pattern | + +Hugo supports render hooks for links, images, blockquotes, code blocks, +headings, passthrough elements, and tables. These hooks are well suited to +presentation changes applied to otherwise meaningful Markdown. + +See the [Hugo render-hook documentation](https://gohugo.io/render-hooks/introduction/). + +## Links + +Use ordinary source-relative Markdown links as the canonical form: + +```markdown +[Transactions](../using-commands/transactions.md) +``` + +A Hugo link render hook can: + +- resolve the source file to its published permalink; +- retain the current base-URL behavior; +- distinguish internal, external, and fragment-only links; +- report unresolved pages during the build. + +An independent Markdown link checker should also run in CI so that link +correctness does not itself depend on Hugo. + +Source-relative links are preferable to versioned site URLs because they work +in repository viewers and generic Markdown tooling without coupling the +source to a particular deployment prefix. + +### Prototype findings + +A link render hook was prototyped and tested (DOC-6909). It is committed at +[`layouts/_default/_markup/render-link.html`](layouts/_default/_markup/render-link.html). +The method: add the hook, convert every `relref` in `content/develop/clients/` +(1,072 calls across 110 files) to plain Markdown links, build, and diff +rendered link targets against a `relref` baseline. + +**Parity is exact.** Across 125 client pages, every internal link resolved +byte-for-byte identically to `relref`, including anchors, mixed-case paths, +bare page-relative paths, and `.md` suffixes. The only remaining differences +were cosmetic percent-encoding of literal parentheses in external URLs +(`(` becomes `%28`), which Goldmark applies to the destination it hands the +hook. + +**`relref` and plain links can coexist.** They do not have to be migrated in a +single pass. When a `relref` is still present its destination is Hugo's internal +shortcode placeholder at hook time; the shortcode expands afterwards and +substitutes the real URL back in — even inside the `href` the hook emitted. The +hook therefore includes a transition guard: if the destination still contains a +shortcode placeholder, it is passed through untouched and no warning is emitted. +Without that guard, an in-progress migration logs one spurious "unresolved" +warning per un-migrated `relref` (26,847 in this repository), which would bury +the genuine broken-link warnings. Remove the guard once every `relref` has been +migrated. + +**Internal links are not only content pages.** The use-case demos link to +companion source files that ship in the page bundle, e.g. `[source](cache.rs)`. +These are page resources, not pages, so `GetPage` cannot see them; the hook +resolves them with `.Page.Resources.GetMatch` before reporting a link as +unresolved. With that in place the remaining build warnings are mostly genuine +signal — real dead links, alias/redirect targets that `GetPage` cannot resolve, +and static-directory files. + +**Installing the hook is the atomic event, not the content migration.** The +hook is global, so on the day it lands it reprocesses every plain Markdown link +that already exists in the repository — for example the reply-type links in the +command reference pages, which are authored as `../../develop/...` relative +links, not `relref`. For those pre-existing links the hook applies harmless +normalisation (relative to absolute, `.md` stripped, trailing slash added); the +targets are unchanged. Content conversion can then proceed gradually, but the +hook itself must be parity-tested against the whole site, not only against the +pages being migrated. + +**A hook must be as robust as Hugo's built-in renderer.** Testing surfaced four +defects that only appear at corpus scale, each of which silently dropped pages +or failed the build: + +1. `.Page.File.Path` panics with a nil-pointer dereference on pages that have no + backing file — content generated via `markdownify` (the command pages) and + shortcode inner content (`note`, `alert`). Use `.Page.Path` instead. +2. `urls.Parse` hard-errors on a malformed destination (a pre-existing + `[Authority]([Authority](https://...))` link) and fails the entire build. + Detect external links with a `findRE` scheme match instead. +3. The unresolved-link fallback duplicated the fragment (`#cas#cas`) because it + re-appended an anchor that the destination already contained. +4. `GetPage` cannot resolve alias (redirect-stub) targets, so links to aliased + paths produce false "unresolved" warnings even though the output is correct. + +Before a migration, run the hook site-wide and fix pre-existing malformed links, +which a hook converts from silently-wrong output into hard build failures. + +### Scaling across diverse sections + +To check that parity was not specific to one section, the conversion was +repeated across four structurally different areas at once — 825 files and about +5,000 `relref` calls — each chosen to exercise a distinct feature: + +| Section | Feature exercised | +|---|---| +| `operate/rs/databases/active-active` | Content mounted under two URL paths | +| `operate/rc` | Image-heavy pages; the target of that mount | +| `operate/kubernetes` | Mixed-case paths and generated API-reference pages | +| `integrate` | Cross-tree links | + +The build produced no errors, dropped no pages, and added no new warnings. +Every rendered-link difference against the `relref` baseline was benign +normalisation (relative to absolute, `.md` stripped, trailing slash added). + +The mounted Active-Active tree is the most demanding case, because the same +source file is published under both `/operate/rs/…` and `/operate/rc/…`. The +hook resolves each relative link to the mount-appropriate permalink — for +example `develop/data-types` renders as +`/operate/rs/databases/active-active/develop/data-types/` under the Software +path and `/operate/rc/databases/active-active/develop/data-types/` under the +Cloud path — matching `relref` exactly on both. This relies on resolving with +`.PageInner`. + +A versioned tree (`operate/rs/7.8`, 326 files) was converted separately. All +435 of its pages carry a `url:` front-matter override, and `GetPage`'s +`.RelPermalink` honours those overrides identically to `relref`: parity was +exact apart from the cosmetic external-parenthesis encoding. This pass also +surfaced and fixed the last render-hook edge case — a malformed link whose +anchor contained an embedded URL with its own `#`. Composing the resolved href +without `safeURL` made Go's template autoescaper blank it to `ZgotmplZ`, and +splitting the anchor on every `#` truncated it. The hook now applies `safeURL` +to the composed URL and splits on the first `#` only, so such anchors are +preserved intact and match the baseline. + +Version-specific links also interact with the archiving tool described below. + +### Tooling that assumes `relref` + +Several build and authoring tools treat `relref` as a literal string — they +parse or generate the shortcode directly. These must be updated or retired as +part of the migration, or they will silently produce wrong output: + +- [`build/version_archiver.py`](build/version_archiver.py) rewrites + `{{< relref "/…" >}}` with a regular expression to make links version-specific + when a versioned documentation snapshot is created. Against plain Markdown + links it matches nothing, so archived versions would keep unversioned links. +- [`build/redisvl_docs_sync.py`](build/redisvl_docs_sync.py) is an importer that + *emits* `relref` syntax when converting upstream RedisVL documentation. It + would need to emit plain Markdown links instead. +- [`.claude/hooks/check_shortcode_paths.py`](.claude/hooks/check_shortcode_paths.py) + validates `relref` target paths on edit. Once links are plain Markdown, that + validation moves to the render hook and an independent link checker. +- [`layouts/partials/process-markdown-content.html`](layouts/partials/process-markdown-content.html) + regex-replaces `relref` when generating the Markdown and JSON outputs (see + "Current alternate-output fragility" below); standard links would let the + render hook handle this instead. + +An audit for `relref` used as a literal string across `build/`, `layouts/`, and +`.claude/` should be part of migration planning. + +## Callouts + +Replace callout shortcodes with GitHub-style blockquote alerts: + +```markdown +> [!WARNING] +> Back up the database before continuing. +``` + +Hugo v0.143.1 supports the necessary blockquote render hooks and alert +metadata. Unsupported Markdown processors still display the content as a +normal blockquote. + +A blockquote render hook is prototyped at +[`layouts/_default/_markup/render-blockquote.html`](layouts/_default/_markup/render-blockquote.html). +It renders `> [!NOTE]` / `> [!WARNING]` / `> [!TIP]` / `> [!INFO]` and similar +alerts with the same styling as the current note/warning/tip/info/alert +shortcodes, and leaves regular blockquotes rendering exactly as before +(verified byte-identical). Crucially, an alert body is native Markdown, so its +links are rendered in the page's context and resolve correctly — unlike the +shortcodes, which `markdownify` their inner content in a page-less context and +so break relative links. **This makes the blockquote migration a prerequisite +for portable, source-relative links inside callouts**, not merely a cosmetic +change. + +Migration should use a Markdown- or shortcode-aware parser rather than regular +expressions. At least 544 existing callouts contain nested shortcodes, and +some callout bodies are very large. Inner shortcodes should be migrated before +their enclosing callout where possible. + +## Images + +Approximately 1,093 of the 1,648 image calls use only a filename and optional +alt text. These can be converted directly: + +```markdown +![Database update status](/images/rc/status.png) +``` + +An image render hook can retain: + +- the link to the full image; +- lightbox behavior; +- URL normalization; +- the existing `#no-click` convention, if it is still required; +- optional titles and semantic styling. + +The remaining image calls use `width` or `class`. The preferred options are: + +1. Remove unnecessary per-image sizing through responsive CSS. +2. Replace arbitrary classes with a small set of semantic styles such as + `inline-icon`, `small`, or `wide`. +3. Use Goldmark image attributes only for genuine exceptions. + +Goldmark attributes are not CommonMark, but they degrade more gracefully than +Hugo shortcode calls. Arbitrary Tailwind class strings should not form part of +the long-term content schema. + +There is also an accessibility opportunity: 418 image shortcode calls do not +currently specify alt text. + +The current image shortcode declares a default width of 75%, but does not +apply it. Only explicitly provided widths appear in the rendered `img` +element. See [`layouts/shortcodes/image.html`](layouts/shortcodes/image.html). + +## Code blocks and tables + +Replace `highlight` and `code` shortcodes with normal fenced code blocks. +Replace `redis-cli` with a `redis-cli` fenced block and use a code-block render +hook to add the current terminal chrome. + +A table render hook can wrap ordinary Markdown tables in a responsive +overflow container. This removes the need for the `table-scrollable` +shortcode without sacrificing the current HTML behavior. + +The repository already follows this progressive-enhancement approach for +Mermaid diagrams, checklists, hierarchies, decision trees, and timelines. See +[`for-ais-only/render_hook_docs/README.md`](for-ais-only/render_hook_docs/README.md). + +## Tabs + +There is no standard Markdown representation for tabs. The best portable +source is ordinary titled sections: + +```markdown +### RESP2 + +RESP2 return information. + +### RESP3 + +RESP3 return information. +``` + +JavaScript can progressively enhance recognized groups into tabs. Other +renderers will show the sections sequentially, which is a useful and +accessible fallback. + +Of the 624 `multitabs` calls, 534 are in command-reference pages. Their +generator should emit RESP headings directly instead of emitting shortcode +syntax. Arbitrary tab sets could use a generator-neutral marker, but such a +marker would still be a custom extension rather than standard Markdown. + +## Dependencies that render hooks should not replace + +The following features generate or transclude content rather than simply +altering Markdown presentation: + +- `clients-example` joins generated example data, client configuration, + source files, and command metadata. +- `jupyter-example` reads source content and configures interactive notebook + behavior. +- `embed-md` transcludes another page through Hugo's page API. +- `embed-yaml`, `embed-code`, and `code-include` read files during rendering. +- `table-children` queries Hugo's page tree and child-page front matter. +- `command-group` generates command lists from data files. +- `rc-supported-regions`, `table-csv`, `external-json`, and similar + shortcodes generate content from local or remote data. + +These should be moved to a generator-neutral preprocessing stage: + +```text +authored Markdown + manifests + shared fragments + | + v + repository build tools + | + v + fully expanded Markdown tree + | + v + Hugo or another site generator +``` + +The expanded Markdown tree becomes a portable intermediate representation. +Hugo and any future generator can consume the same tree. The authoring layer +may retain a small project-specific directive or manifest schema, but it no +longer depends on Go templates or Hugo's page APIs. + +## Page-model and build dependencies + +Removing shortcodes would not by itself make the complete site +generator-independent. The repository also relies on: + +- 992 `_index.md` files for section pages and hierarchy; +- front matter fields such as `weight`, `alwaysopen`, `hideListLinks`, `url`, + `aliases`, `type`, `layout`, and `cascade`; +- 701 alias declarations; +- page-tree navigation and sorting; +- Hugo's data directory and site configuration; +- Hugo's asset pipeline; +- related-content, taxonomy, menu, and template APIs; +- alternate Markdown and JSON output formats; +- a Hugo module mount that exposes the same Active-Active source under both + Redis Software and Redis Cloud paths. + +YAML front matter is broadly supported by documentation generators, but the +project should define its own metadata schema. Generator adapters can then map +that schema to Hugo or a future system. + +The content mount is configured in [`config.toml`](config.toml). A portable +replacement would materialize the duplicated tree during preprocessing or +model the shared content explicitly in a manifest. + +## Raw HTML + +An approximate scan found recognized raw HTML elements outside code fences in +1,819 Markdown files. Much of the volume is generated table and REST API +markup, including: + +- tables and table cells; +- `details` and `summary`; +- `span`, `br`, and `nobr`; +- raw links and images; +- formatting elements. + +Raw HTML is not Hugo-specific, and many Markdown systems support it, but its +security policy and styling vary between renderers. The current repository +explicitly enables unsafe Goldmark rendering in [`config.toml`](config.toml). + +Raw HTML should therefore be treated as a secondary portability workstream. +Generated tables, `
`, ``, ``, and manually constructed +`
` blocks are the best initial targets. + +## Current alternate-output fragility + +The Markdown and JSON output pipeline already has to recreate Hugo shortcode +behavior through regular-expression replacements. It explicitly handles a +subset of constructs, repeatedly unescapes HTML entities, and finally removes +all remaining shortcode tags. + +See +[`layouts/partials/process-markdown-content.html`](layouts/partials/process-markdown-content.html). + +This is evidence of the maintenance cost of storing presentation macros in the +source. Standardizing source Markdown would simplify HTML rendering, AI-facing +Markdown, JSON generation, indexing, and future migrations at the same time. + +## Recommended migration sequence + +### Phase 1: establish conventions and adapters + +1. Define canonical conventions for links, images, callouts, code blocks, and + tables. +2. Add link, image, blockquote, and table render hooks. +3. Add generator-independent link and content linting to CI. +4. Prevent new uses of replaceable shortcodes. + +### Phase 2: remove high-volume presentation shortcodes + +1. Migrate `relref` calls to normal Markdown links. +2. Migrate callouts after converting their nested shortcodes. +3. Convert simple image calls and address missing alt text. +4. Convert code wrappers to fenced code blocks. +5. Remove `table-scrollable` through the table hook. +6. Update importers and generators so they emit the new conventions. + +### Phase 3: improve progressive enhancement + +1. Change generated RESP tabs to ordinary headings. +2. Progressively enhance appropriate heading groups into tabs. +3. Replace small presentational shortcodes with semantic Markdown or HTML. +4. Continue using typed code fences for diagrams and structured interactive + content. + +### Phase 4: isolate true generation features + +1. Define a generator-neutral schema for includes and data-driven components. +2. Expand those components into a portable Markdown build tree before Hugo + runs. +3. Move mounted or duplicated content into the same preprocessing layer. +4. Define and document the portable front matter schema. + +### Phase 5: prove portability + +1. Feed the expanded Markdown tree to a second renderer in CI. +2. Compare page counts, resolved links, headings, metadata, and essential + semantic content. +3. Treat visual parity as an adapter concern rather than an authoring-format + concern. + +## Conclusion + +The repository is strongly tied to Hugo as a complete publishing system, but +the authoring format can be made substantially more portable without replacing +Hugo. + +The immediate goal should be: + +> Store semantic, readable Markdown in `content/`, and use Hugo only as a +> rendering and publishing adapter. + +Render hooks provide a practical route for links, images, callouts, code +blocks, tables, and structured fenced blocks. True content-generation +features should be isolated behind a preprocessing boundary. Together, these +changes would reduce the cost and risk of evaluating another documentation +platform while preserving the existing Hugo site. diff --git a/layouts/_default/_markup/render-blockquote.html b/layouts/_default/_markup/render-blockquote.html new file mode 100644 index 0000000000..82e784232b --- /dev/null +++ b/layouts/_default/_markup/render-blockquote.html @@ -0,0 +1,32 @@ +{{- /* + Blockquote render hook. + + GitHub-style alert blockquotes (`> [!NOTE]`, `> [!WARNING]`, `> [!TIP]`, + `> [!INFO]`, `> [!IMPORTANT]`, `> [!CAUTION]`, …) render with the same styling + as the note/warning/tip/info/alert shortcodes (see + layouts/partials/components/alert.html), so callouts can be authored as + portable Markdown instead of shortcodes. + + Because the alert body is native Markdown, `.Text` is rendered in the page's + context — links inside a callout resolve correctly (unlike the shortcodes, + which markdownify their inner content in a page-less context). + + Regular blockquotes keep Hugo's default rendering. + + Prototype from DOC-6909 (investigate alternatives to Hugo shortcodes). +*/ -}} +{{- if eq .Type "alert" -}} + {{- $title := or .AlertTitle (title .AlertType) -}} +
+
{{ partial "icons/alert-circle.html" }}
+
+ {{ with $title }} +
{{ . | safeHTML }}:
+ {{ end }} + {{- .Text -}} +
+
+{{- else -}} +
+{{ .Text }}
+{{ end -}} diff --git a/layouts/_default/_markup/render-link.html b/layouts/_default/_markup/render-link.html new file mode 100644 index 0000000000..b1dd5c1f51 --- /dev/null +++ b/layouts/_default/_markup/render-link.html @@ -0,0 +1,73 @@ +{{- /* + Link render hook: resolves internal Markdown links to their published + permalink, recreating the behaviour of the `relref` shortcode so that + `[text](/path)` can replace `[text]({{< relref "/path" >}})`. + + Prototype from DOC-6909 (investigate alternatives to Hugo shortcodes). + Verified against a `relref` baseline: internal links render byte-identically; + pre-existing plain links are normalised (relative -> absolute, `.md` stripped, + trailing slash added). See HUGO_DEPENDENCY_ASSESSMENT.md. + + Resolution uses .PageInner, not .Page, so relative links resolve against the + page whose Markdown contains the link even when content is transcluded. + + NOTE: remove the placeholder transition guard once every `relref` has been + migrated to a plain Markdown link. +*/ -}} +{{- $dest := .Destination -}} +{{- $text := .Text -}} +{{- $title := .Title -}} +{{- if strings.Contains $dest "HAHAHUGOSHORTCODE" -}} + {{- /* A relref/shortcode has not been substituted yet; leave the placeholder + for Hugo to fill in and do not attempt to resolve or warn. */ -}} + {{ $text | safeHTML }} +{{- else if strings.HasPrefix $dest "#" -}} + {{- /* Fragment-only: a same-page internal link, emitted unchanged. */ -}} + {{ $text | safeHTML }} +{{- else if or (findRE "^[a-zA-Z][a-zA-Z0-9+.\\-]*:" $dest) (strings.HasPrefix $dest "//") -}} + {{- /* External: a URL scheme or protocol-relative link. Detected with findRE + rather than urls.Parse, which hard-errors on malformed destinations. */ -}} + {{ $text | safeHTML }} +{{- else -}} + {{- $path := $dest -}} + {{- $anchor := "" -}} + {{- /* Split on the first `#` only, so an anchor that itself contains `#` + (malformed but present in the corpus) is preserved intact. */ -}} + {{- if strings.Contains $path "#" -}} + {{- $parts := split $path "#" -}} + {{- $path = index $parts 0 -}} + {{- $anchor = printf "#%s" (delimit (after 1 $parts) "#") -}} + {{- end -}} + {{- /* Strip a leading `./` so explicit same-directory links resolve; leave + `../` alone, which GetPage resolves relative to the current page. */ -}} + {{- $path = strings.TrimPrefix "./" $path -}} + {{- /* Repo-root-relative links (`/content/...`) resolve against the repository + root in both GitHub's rendered view and VS Code, which avoids the long + `../../../` chains that source-relative links need for cross-section + targets. Strip the content-mount prefix so GetPage, which expects a + content-relative path, resolves them. Requires the trailing slash so a + hypothetical `/contentfoo` is left alone. */ -}} + {{- if strings.HasPrefix $path "/content/" -}} + {{- $path = strings.TrimPrefix "/content" $path -}} + {{- end -}} + {{- /* Normalise source-relative Markdown links so they resolve in the repo + (VS Code, GitHub) and here: drop the `.md`, and a trailing `/_index` + or `/index` from links that point at a section or leaf-bundle file. */ -}} + {{- $lookup := strings.TrimSuffix ".md" $path -}} + {{- $lookup = strings.TrimSuffix "/_index" $lookup -}} + {{- $lookup = strings.TrimSuffix "/index" $lookup -}} + {{- $target := .PageInner.GetPage $lookup -}} + {{- if $target -}} + {{ $text | safeHTML }} + {{- else -}} + {{- /* Not a content page: try a page-bundle resource (e.g. the source + files linked with [source](cache.rs) in the use-case demos). */ -}} + {{- $res := .PageInner.Resources.GetMatch $path -}} + {{- if $res -}} + {{ $text | safeHTML }} + {{- else -}} + {{- warnf "render-link: unresolved link %q on page %q" $dest .PageInner.Path -}} + {{ $text | safeHTML }} + {{- end -}} + {{- end -}} +{{- end -}}