Skip to content

Commit

Permalink
tpl/tplimpl: Add details shortcode
Browse files Browse the repository at this point in the history
- Add new shortcode to render details HTML element.
- Implement integration tests to check: default state, custom summary, open state,  attribute sanitization, allowed attributes, and localization of default summary text.
- Update docs to include details shortcode.

Closes # 13090
  • Loading branch information
racehd authored and bep committed Dec 13, 2024
1 parent 9dfa112 commit 4f130f6
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 0 deletions.
34 changes: 34 additions & 0 deletions docs/content/en/content-management/shortcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,40 @@ Example usage:

Although you can call this shortcode using the `{{</* */>}}` notation, computationally it is more efficient to call it using the `{{%/* */%}}` notation as shown above.

### details

{{< new-in 0.140.0 >}}

{{% note %}}
To override Hugo's embedded `details` shortcode, copy the [source code] to a file with the same name in the layouts/shortcodes directory.

This may be useful if you are wanting access to more global HTML attributes.

[source code]: {{% eturl details %}}
{{% /note %}}

Use the `details` shortcode to generate a collapsible details HTML element. For example:

```text
{{</* details summary="Custom Summary Text" */>}}
Showing custom `summary` text.
{{</* /details */>}}
```

Additional examples can be found in the source code. The `details` shortcode can use the following named arguments:

summary
: (`string`) Optional. Specifies the content of the child summary element. Default is "Details"

open
: (`bool`) Optional. Whether to initially display the contents of the details element. Default is `false`.

name
: (`string`) Optional. The value of the element's name attribute.

class
: (`string`) Optional. The value of the element's class attribute.

### figure

{{% note %}}
Expand Down
1 change: 1 addition & 0 deletions docs/data/embedded_template_urls.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

# Shortcodes
'comment' = 'shortcodes/comment.html'
'details' = 'shortcodes/details.html'
'figure' = 'shortcodes/figure.html'
'gist' = 'shortcodes/gist.html'
'highlight' = 'shortcodes/highlight.html'
Expand Down
68 changes: 68 additions & 0 deletions tpl/tplimpl/embedded/templates/shortcodes/details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{{- /*
Renders an HTML details element.

@param {string} [summary] The content of the child summary element.
@param {bool} [open=false] Whether to initially display the contents of the details element.
@param {string} [class] The value of the element's class attribute.
@param {string} [name] The value of the element's name attribute.

@reference https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details

@examples

{{< details >}}
A basic collapsible section.
{{< /details >}}

{{< details summary="Custom Summary Text" >}}
Showing custom `summary` text.
{{< /details >}}

{{< details summary="Open Details" open=true >}}
Contents displayed initially by using `open`.
{{< /details >}}

{{< details summary="Styled Content" class="my-custom-class" >}}
Content can be styled with CSS by specifying a `class`.

Target details element:

```css
details.my-custom-class { }
```

Target summary element:

```css
details.my-custom-class > summary > * { }
```

Target inner content:

```css
details.my-custom-class > :not(summary) { }
```
{{< /details >}}

{{< details summary="Grouped Details" name="my-details" >}}
Specifying a `name` allows elements to be connected, with only one able to be open at a time.
{{< /details >}}

*/}}

{{- /* Get arguments. */}}
{{- $summary := or (.Get "summary") (T "shortcodes.details") "Details" }}
{{- $class := or (.Get "class") "" }}
{{- $name := or (.Get "name") "" }}
{{- $open := false }}
{{- if in (slice "false" false 0) (.Get "open") }}
{{- $open = false }}
{{- else if in (slice "true" true 1) (.Get "open")}}
{{- $open = true }}
{{- end }}

{{- /* Render. */}}
<details{{- if $open }} open{{ end }}{{- if $name }} name="{{ $name }}"{{- end }}{{- if $class }} class="{{ $class }}"{{- end }}>
<summary>{{ $summary | .Page.RenderString }}</summary>
{{ .Inner | .Page.RenderString (dict "display" "block") -}}
</details>
115 changes: 115 additions & 0 deletions tpl/tplimpl/tplimpl_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,118 @@ a{{< comment >}}b{{< /comment >}}c
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", "<p>ac</p>")
}

func TestDetailsShortcode(t *testing.T) {
t.Parallel()

files := `
-- hugo.toml --
disableKinds = ['rss','section','sitemap','taxonomy','term']
defaultContentLanguage = "en"
[languages]
[languages.en]
weight = 1
[languages.es]
weight = 2
-- i18n/en.toml --
[shortcodes.details]
other = "Details"
-- i18n/es.toml --
[shortcodes.details]
other = "Detalles"
-- layouts/_default/single.html --
{{ .Content }}
-- content/d1.md --
---
title: Default State Test
---
{{< details >}}
Basic example without summary
{{< /details >}}
-- content/d2.md --
---
title: Custom Summary Test
---
{{< details summary="Custom Summary" >}}
Example with custom summary text
{{< /details >}}
-- content/d3.md --
---
title: Open State Test
---
{{< details summary="Test Open State" open="true" >}}
Example with open state
{{< /details >}}
-- content/d4.md --
---
title: Attributes Test
---
{{< details summary="Test Attribute sanitization" style="color: red; font-weight: bold; background-color: #eee" onclick="alert('test')" >}}
Example testing attribute sanitization
{{< /details >}}
-- content/d5.md --
---
title: Class Test
---
{{< details class="custom-class" >}}
Example with allowed class attribute
{{< /details >}}
-- content/d6.md --
---
title: Name Test
---
{{< details name="custom-name" >}}
Example with allowed name attribute
{{< /details >}}
-- content/d7.es.md --
---
title: Localization Test
---
{{< details >}}
Localization example without summary
{{< /details >}}
`
b := hugolib.Test(t, files)

// Test1: default state (closed by default)
b.AssertFileContentEquals("public/d1/index.html",
"\n<details>\n <summary>Details</summary>\n <p>Basic example without summary</p>\n</details>\n",
)
content1 := b.FileContent("public/d1/index.html")
c := qt.New(t)
c.Assert(content1, qt.Not(qt.Contains), "open")

// Test2: custom summary
b.AssertFileContentEquals("public/d2/index.html",
"\n<details>\n <summary>Custom Summary</summary>\n <p>Example with custom summary text</p>\n</details>\n",
)

// Test3: open state
b.AssertFileContentEquals("public/d3/index.html",
"\n<details open>\n <summary>Test Open State</summary>\n <p>Example with open state</p>\n</details>\n",
)

// Test4: Test sanitization
b.AssertFileContentEquals("public/d4/index.html",
"\n<details>\n <summary>Test Attribute sanitization</summary>\n <p>Example testing attribute sanitization</p>\n</details>\n",
)
content4 := b.FileContent("public/d4/index.html")
c.Assert(content4, qt.Not(qt.Contains), "style")
c.Assert(content4, qt.Not(qt.Contains), "onclick")
c.Assert(content4, qt.Not(qt.Contains), "alert")

// Test5: class attribute
b.AssertFileContentEquals("public/d5/index.html",
"\n<details class=\"custom-class\">\n <summary>Details</summary>\n <p>Example with allowed class attribute</p>\n</details>\n",
)

// Test6: name attribute
b.AssertFileContentEquals("public/d6/index.html",
"\n<details name=\"custom-name\">\n <summary>Details</summary>\n <p>Example with allowed name attribute</p>\n</details>\n",
)

// Test7: localization
b.AssertFileContentEquals("public/es/d7/index.html",
"\n<details>\n <summary>Detalles</summary>\n <p>Localization example without summary</p>\n</details>\n",
)
}

0 comments on commit 4f130f6

Please sign in to comment.