Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tpl/tplimpl: Add details shortcode #13100

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

racehd marked this conversation as resolved.
Show resolved Hide resolved
{{< 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.

[source code]: {{% eturl details %}}
racehd marked this conversation as resolved.
Show resolved Hide resolved
{{% /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`.

You can also pass global HTML attributes when calling the shortcode. For example:

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

### 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
72 changes: 72 additions & 0 deletions tpl/tplimpl/embedded/templates/shortcodes/details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{{- /*
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 {object} [...params] Additional HTML attributes passed directly to the details element.

@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 "details") "Details" }}
{{- $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 }}
{{- range $k, $v := .Params }}
{{- if not (or (in (slice "open" "summary") $k) (strings.HasPrefix $k "on")) }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end }}
{{- end -}}
>
<summary>{{ $summary | .Page.RenderString }}</summary>
{{ .Inner | .Page.RenderString (dict "display" "block") -}}
</details>
102 changes: 102 additions & 0 deletions tpl/tplimpl/tplimpl_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,105 @@ 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 --
[details]
other = "Details"
-- i18n/es.toml --
[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;" 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.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: safeHTML sanitization
b.AssertFileContentEquals("public/d4/index.html",
"\n<details style=\"color: red;\">\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), "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: localization
b.AssertFileContentEquals("public/es/d6/index.html",
"\n<details>\n <summary>Detalles</summary>\n <p>Localization example without summary</p>\n</details>\n",
)
}