Skip to content

Commit 4f130f6

Browse files
racehdbep
authored andcommittedDec 13, 2024·
tpl/tplimpl: Add details shortcode
- 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
1 parent 9dfa112 commit 4f130f6

File tree

4 files changed

+218
-0
lines changed

4 files changed

+218
-0
lines changed
 

‎docs/content/en/content-management/shortcodes.md

+34
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,40 @@ Example usage:
9494

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

97+
### details
98+
99+
{{< new-in 0.140.0 >}}
100+
101+
{{% note %}}
102+
To override Hugo's embedded `details` shortcode, copy the [source code] to a file with the same name in the layouts/shortcodes directory.
103+
104+
This may be useful if you are wanting access to more global HTML attributes.
105+
106+
[source code]: {{% eturl details %}}
107+
{{% /note %}}
108+
109+
Use the `details` shortcode to generate a collapsible details HTML element. For example:
110+
111+
```text
112+
{{</* details summary="Custom Summary Text" */>}}
113+
Showing custom `summary` text.
114+
{{</* /details */>}}
115+
```
116+
117+
Additional examples can be found in the source code. The `details` shortcode can use the following named arguments:
118+
119+
summary
120+
: (`string`) Optional. Specifies the content of the child summary element. Default is "Details"
121+
122+
open
123+
: (`bool`) Optional. Whether to initially display the contents of the details element. Default is `false`.
124+
125+
name
126+
: (`string`) Optional. The value of the element's name attribute.
127+
128+
class
129+
: (`string`) Optional. The value of the element's class attribute.
130+
97131
### figure
98132

99133
{{% note %}}

‎docs/data/embedded_template_urls.toml

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
# Shortcodes
2727
'comment' = 'shortcodes/comment.html'
28+
'details' = 'shortcodes/details.html'
2829
'figure' = 'shortcodes/figure.html'
2930
'gist' = 'shortcodes/gist.html'
3031
'highlight' = 'shortcodes/highlight.html'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{{- /*
2+
Renders an HTML details element.
3+
4+
@param {string} [summary] The content of the child summary element.
5+
@param {bool} [open=false] Whether to initially display the contents of the details element.
6+
@param {string} [class] The value of the element's class attribute.
7+
@param {string} [name] The value of the element's name attribute.
8+
9+
@reference https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details
10+
11+
@examples
12+
13+
{{< details >}}
14+
A basic collapsible section.
15+
{{< /details >}}
16+
17+
{{< details summary="Custom Summary Text" >}}
18+
Showing custom `summary` text.
19+
{{< /details >}}
20+
21+
{{< details summary="Open Details" open=true >}}
22+
Contents displayed initially by using `open`.
23+
{{< /details >}}
24+
25+
{{< details summary="Styled Content" class="my-custom-class" >}}
26+
Content can be styled with CSS by specifying a `class`.
27+
28+
Target details element:
29+
30+
```css
31+
details.my-custom-class { }
32+
```
33+
34+
Target summary element:
35+
36+
```css
37+
details.my-custom-class > summary > * { }
38+
```
39+
40+
Target inner content:
41+
42+
```css
43+
details.my-custom-class > :not(summary) { }
44+
```
45+
{{< /details >}}
46+
47+
{{< details summary="Grouped Details" name="my-details" >}}
48+
Specifying a `name` allows elements to be connected, with only one able to be open at a time.
49+
{{< /details >}}
50+
51+
*/}}
52+
53+
{{- /* Get arguments. */}}
54+
{{- $summary := or (.Get "summary") (T "shortcodes.details") "Details" }}
55+
{{- $class := or (.Get "class") "" }}
56+
{{- $name := or (.Get "name") "" }}
57+
{{- $open := false }}
58+
{{- if in (slice "false" false 0) (.Get "open") }}
59+
{{- $open = false }}
60+
{{- else if in (slice "true" true 1) (.Get "open")}}
61+
{{- $open = true }}
62+
{{- end }}
63+
64+
{{- /* Render. */}}
65+
<details{{- if $open }} open{{ end }}{{- if $name }} name="{{ $name }}"{{- end }}{{- if $class }} class="{{ $class }}"{{- end }}>
66+
<summary>{{ $summary | .Page.RenderString }}</summary>
67+
{{ .Inner | .Page.RenderString (dict "display" "block") -}}
68+
</details>

‎tpl/tplimpl/tplimpl_integration_test.go

+115
Original file line numberDiff line numberDiff line change
@@ -600,3 +600,118 @@ a{{< comment >}}b{{< /comment >}}c
600600
b := hugolib.Test(t, files)
601601
b.AssertFileContent("public/index.html", "<p>ac</p>")
602602
}
603+
604+
func TestDetailsShortcode(t *testing.T) {
605+
t.Parallel()
606+
607+
files := `
608+
-- hugo.toml --
609+
disableKinds = ['rss','section','sitemap','taxonomy','term']
610+
defaultContentLanguage = "en"
611+
[languages]
612+
[languages.en]
613+
weight = 1
614+
[languages.es]
615+
weight = 2
616+
-- i18n/en.toml --
617+
[shortcodes.details]
618+
other = "Details"
619+
-- i18n/es.toml --
620+
[shortcodes.details]
621+
other = "Detalles"
622+
-- layouts/_default/single.html --
623+
{{ .Content }}
624+
-- content/d1.md --
625+
---
626+
title: Default State Test
627+
---
628+
{{< details >}}
629+
Basic example without summary
630+
{{< /details >}}
631+
-- content/d2.md --
632+
---
633+
title: Custom Summary Test
634+
---
635+
{{< details summary="Custom Summary" >}}
636+
Example with custom summary text
637+
{{< /details >}}
638+
-- content/d3.md --
639+
---
640+
title: Open State Test
641+
---
642+
{{< details summary="Test Open State" open="true" >}}
643+
Example with open state
644+
{{< /details >}}
645+
-- content/d4.md --
646+
---
647+
title: Attributes Test
648+
---
649+
{{< details summary="Test Attribute sanitization" style="color: red; font-weight: bold; background-color: #eee" onclick="alert('test')" >}}
650+
Example testing attribute sanitization
651+
{{< /details >}}
652+
-- content/d5.md --
653+
---
654+
title: Class Test
655+
---
656+
{{< details class="custom-class" >}}
657+
Example with allowed class attribute
658+
{{< /details >}}
659+
-- content/d6.md --
660+
---
661+
title: Name Test
662+
---
663+
{{< details name="custom-name" >}}
664+
Example with allowed name attribute
665+
{{< /details >}}
666+
-- content/d7.es.md --
667+
---
668+
title: Localization Test
669+
---
670+
{{< details >}}
671+
Localization example without summary
672+
{{< /details >}}
673+
`
674+
b := hugolib.Test(t, files)
675+
676+
// Test1: default state (closed by default)
677+
b.AssertFileContentEquals("public/d1/index.html",
678+
"\n<details>\n <summary>Details</summary>\n <p>Basic example without summary</p>\n</details>\n",
679+
)
680+
content1 := b.FileContent("public/d1/index.html")
681+
c := qt.New(t)
682+
c.Assert(content1, qt.Not(qt.Contains), "open")
683+
684+
// Test2: custom summary
685+
b.AssertFileContentEquals("public/d2/index.html",
686+
"\n<details>\n <summary>Custom Summary</summary>\n <p>Example with custom summary text</p>\n</details>\n",
687+
)
688+
689+
// Test3: open state
690+
b.AssertFileContentEquals("public/d3/index.html",
691+
"\n<details open>\n <summary>Test Open State</summary>\n <p>Example with open state</p>\n</details>\n",
692+
)
693+
694+
// Test4: Test sanitization
695+
b.AssertFileContentEquals("public/d4/index.html",
696+
"\n<details>\n <summary>Test Attribute sanitization</summary>\n <p>Example testing attribute sanitization</p>\n</details>\n",
697+
)
698+
content4 := b.FileContent("public/d4/index.html")
699+
c.Assert(content4, qt.Not(qt.Contains), "style")
700+
c.Assert(content4, qt.Not(qt.Contains), "onclick")
701+
c.Assert(content4, qt.Not(qt.Contains), "alert")
702+
703+
// Test5: class attribute
704+
b.AssertFileContentEquals("public/d5/index.html",
705+
"\n<details class=\"custom-class\">\n <summary>Details</summary>\n <p>Example with allowed class attribute</p>\n</details>\n",
706+
)
707+
708+
// Test6: name attribute
709+
b.AssertFileContentEquals("public/d6/index.html",
710+
"\n<details name=\"custom-name\">\n <summary>Details</summary>\n <p>Example with allowed name attribute</p>\n</details>\n",
711+
)
712+
713+
// Test7: localization
714+
b.AssertFileContentEquals("public/es/d7/index.html",
715+
"\n<details>\n <summary>Detalles</summary>\n <p>Localization example without summary</p>\n</details>\n",
716+
)
717+
}

0 commit comments

Comments
 (0)
Please sign in to comment.