Skip to content

Commit b518c57

Browse files
authored
Merge pull request #172 from layer5io/hextra-shortcodes
Add Hextra shortcodes (Bootstrap 5 port)
2 parents 2b04ce3 + 52e60cf commit b518c57

23 files changed

Lines changed: 1262 additions & 0 deletions
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// ============================================================================
2+
// Hextra shortcodes – custom styles for Bootstrap 5 port
3+
// ============================================================================
4+
5+
// ---------------------------------------------------------------------------
6+
// Callout
7+
// ---------------------------------------------------------------------------
8+
.hextra-callout {
9+
border-left-width: 4px !important;
10+
11+
.hextra-callout-content {
12+
> :first-child {
13+
margin-top: 0;
14+
}
15+
> :last-child {
16+
margin-bottom: 0;
17+
}
18+
}
19+
}
20+
21+
// ---------------------------------------------------------------------------
22+
// Card
23+
// ---------------------------------------------------------------------------
24+
.hextra-cards {
25+
.hextra-card {
26+
color: inherit;
27+
transition: box-shadow 0.2s ease, border-color 0.2s ease;
28+
29+
&:hover {
30+
border-color: $primary;
31+
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);
32+
color: inherit;
33+
}
34+
35+
.hextra-card-icon svg {
36+
width: 1.5rem;
37+
color: rgba(0, 0, 0, 0.2);
38+
transition: color 0.3s ease;
39+
}
40+
41+
&:hover .hextra-card-icon svg {
42+
color: currentColor;
43+
}
44+
}
45+
}
46+
47+
// ---------------------------------------------------------------------------
48+
// Details
49+
// ---------------------------------------------------------------------------
50+
.hextra-details {
51+
> summary {
52+
// Remove default marker / triangle in all browsers.
53+
&::marker,
54+
&::-webkit-details-marker {
55+
display: none;
56+
}
57+
}
58+
59+
.hextra-details-chevron {
60+
transition: transform 0.2s ease;
61+
flex-shrink: 0;
62+
}
63+
64+
&[open] > summary .hextra-details-chevron {
65+
transform: rotate(90deg);
66+
}
67+
}
68+
69+
// ---------------------------------------------------------------------------
70+
// Steps
71+
// ---------------------------------------------------------------------------
72+
.hextra-steps {
73+
counter-reset: step;
74+
border-color: $border-color;
75+
76+
:where(h2, h3, h4, h5, h6):not(.no-step-marker) {
77+
counter-increment: step;
78+
position: relative;
79+
80+
&::before {
81+
content: counter(step);
82+
position: absolute;
83+
width: 33px;
84+
height: 33px;
85+
border-radius: 50%;
86+
background-color: $light;
87+
border: 4px solid $body-bg;
88+
color: $secondary;
89+
font-size: 1rem;
90+
font-weight: normal;
91+
text-align: center;
92+
line-height: 25px;
93+
left: -41px;
94+
}
95+
}
96+
}
97+
98+
// ---------------------------------------------------------------------------
99+
// Tabs – extend Bootstrap nav-tabs styling
100+
// ---------------------------------------------------------------------------
101+
.hextra-tabs-wrapper {
102+
margin-bottom: 1rem;
103+
}
104+
105+
// ---------------------------------------------------------------------------
106+
// FileTree
107+
// ---------------------------------------------------------------------------
108+
.hextra-filetree {
109+
.hextra-filetree-folder {
110+
border: none;
111+
background: none;
112+
font-size: inherit;
113+
114+
&:hover {
115+
opacity: 0.6;
116+
}
117+
}
118+
119+
svg {
120+
flex-shrink: 0;
121+
}
122+
}
123+
124+
// ---------------------------------------------------------------------------
125+
// Jupyter
126+
// ---------------------------------------------------------------------------
127+
.hextra-jupyter-code-cell {
128+
.hextra-jupyter-outputs {
129+
scrollbar-width: thin;
130+
131+
pre {
132+
font-size: 0.75rem;
133+
overflow: auto;
134+
max-width: 100%;
135+
}
136+
}
137+
}
138+
139+
// ---------------------------------------------------------------------------
140+
// PDF
141+
// ---------------------------------------------------------------------------
142+
.hextra-pdf {
143+
min-height: 32rem;
144+
}

assets/scss/_styles_project.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@import "search_project.scss";
88
@import "summary.scss";
99
@import "lab.scss";
10+
@import "hextra_shortcodes.scss";
1011

1112
// Page Layout Sections
1213
.td-home {

data/hextra/icons.yaml

Lines changed: 312 additions & 0 deletions
Large diffs are not rendered by default.

hugo.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ markup:
5656
parser:
5757
attribute:
5858
block: true
59+
renderer:
60+
unsafe: true
5961
highlight:
6062
# See a complete list of available styles at https://xyproto.github.io/splash/docs/all.html
6163
# style: tango
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{{/* Utility to resolve file paths from absolute, relative path, or URL. */}}
2+
3+
{{- $path := .path -}}
4+
{{- $page := .page -}}
5+
6+
{{- $isLocal := not (urls.Parse $path).Scheme -}}
7+
{{- $isPage := and (eq $page.Kind "page") (not $page.BundleType) -}}
8+
{{- $startsWithSlash := hasPrefix $path "/" -}}
9+
{{- $startsWithRelative := hasPrefix $path "../" -}}
10+
11+
{{- if and $path $isLocal -}}
12+
{{- if $startsWithSlash -}}
13+
{{/* File under static directory */}}
14+
{{- $path = (relURL (strings.TrimPrefix "/" $path)) -}}
15+
{{- else if and $isPage (not $startsWithRelative) -}}
16+
{{/* File is a sibling to the individual page file */}}
17+
{{ $path = (printf "../%s" $path) }}
18+
{{- end -}}
19+
{{- end -}}
20+
21+
{{- return $path -}}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{{/* Render raw SVG icon from site.Data.hextra.icons */}}
2+
{{- $icon := index site.Data.hextra.icons .name -}}
3+
4+
{{- if not $icon -}}
5+
{{ errorf "hextra icon %q not found in data/hextra/icons.yaml" .name }}
6+
{{- end -}}
7+
8+
{{- $icon = $icon | safeHTML -}}
9+
10+
{{- if .attributes -}}
11+
{{- $icon = replaceRE "<svg" (printf "<svg %s" .attributes) $icon -}}
12+
{{- end -}}
13+
14+
{{- return ($icon | safeHTML) -}}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
{{- /*
2+
Hextra asciinema player shortcode (Bootstrap 5 port).
3+
Embeds an asciinema-player for terminal recordings.
4+
5+
@param {string} file Path or URL of the .cast file (or positional param 0).
6+
@param {string} theme Player theme (default "asciinema").
7+
@param {number} speed Playback speed (default 1).
8+
@param {bool} autoplay Auto-play on load (default false).
9+
@param {bool} loop Loop playback (default false).
10+
@param {string} poster Poster / thumbnail specification.
11+
@param {string} markers Comma-separated time markers.
12+
13+
@example {{< hextra/asciinema file="demo.cast" >}}
14+
*/ -}}
15+
16+
{{- $castFile := .Get "file" | default (.Get 0) -}}
17+
{{- $theme := .Get "theme" | default "asciinema" -}}
18+
{{- $speed := .Get "speed" | default 1 -}}
19+
{{- $autoplay := .Get "autoplay" | default false -}}
20+
{{- $loop := .Get "loop" | default false -}}
21+
{{- $poster := .Get "poster" | default "" -}}
22+
{{- $markers := .Get "markers" | default "" -}}
23+
24+
{{- /* Handle file path: support local files, absolute paths, and remote URLs */ -}}
25+
{{- $isLocal := not (urls.Parse $castFile).Scheme -}}
26+
{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}}
27+
28+
{{- if $isLocal -}}
29+
{{- $found := false -}}
30+
31+
{{- if not $isPage -}}
32+
{{- with .Page.Resources.Get $castFile -}}
33+
{{- $castFile = .RelPermalink -}}
34+
{{- $found = true -}}
35+
{{- end -}}
36+
{{- end -}}
37+
38+
{{- if not $found -}}
39+
{{- with resources.Get $castFile -}}
40+
{{- $castFile = .RelPermalink -}}
41+
{{- $found = true -}}
42+
{{- end -}}
43+
{{- end -}}
44+
45+
{{- if not $found -}}
46+
{{- if hasPrefix $castFile "/" -}}
47+
{{- $castFile = relURL (strings.TrimPrefix "/" $castFile) -}}
48+
{{- else -}}
49+
{{- $castFile = relURL $castFile -}}
50+
{{- end -}}
51+
{{- end -}}
52+
{{- end -}}
53+
54+
{{- /* Build marker configuration using jsonify for proper escaping */ -}}
55+
{{- $markerConfig := "" -}}
56+
{{- if $markers -}}
57+
{{- $markerParts := slice -}}
58+
{{- range (split $markers ",") -}}
59+
{{- $item := trim . " " -}}
60+
{{- $colonIndex := findRE ":" $item -}}
61+
{{- if $colonIndex -}}
62+
{{- $pair := split $item ":" -}}
63+
{{- if ge (len $pair) 2 -}}
64+
{{- $time := float (trim (index $pair 0) " ") -}}
65+
{{- $label := trim (index $pair 1) " " -}}
66+
{{- $markerParts = $markerParts | append (slice $time $label) -}}
67+
{{- end -}}
68+
{{- else -}}
69+
{{- $markerParts = $markerParts | append (float $item) -}}
70+
{{- end -}}
71+
{{- end -}}
72+
{{- $markerConfig = $markerParts | jsonify -}}
73+
{{- end -}}
74+
75+
{{- /* Mark page as using asciinema for conditional script loading */ -}}
76+
{{- .Page.Store.Set "hasAsciinema" true -}}
77+
78+
<div class="hextra-asciinema-player"
79+
role="region"
80+
aria-label="{{ (T "terminalRecording") | default "Terminal recording" }}"
81+
data-cast-file="{{ $castFile }}"
82+
data-theme="{{ $theme }}"
83+
data-speed="{{ $speed }}"
84+
data-autoplay="{{ $autoplay }}"
85+
data-loop="{{ $loop }}"
86+
{{- if ne $poster "" }} data-poster="{{ $poster | safeURL }}"{{- end -}}
87+
{{- if $markerConfig }} data-markers='{{ $markerConfig }}'{{- end -}}>
88+
</div>
89+
90+
{{- /* Include asciinema-player assets once per page */ -}}
91+
{{- if not (.Page.Store.Get "hextraAsciinemaLoaded") -}}
92+
{{- .Page.Store.Set "hextraAsciinemaLoaded" true -}}
93+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.css">
94+
<script src="https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js"></script>
95+
<script>
96+
document.addEventListener('DOMContentLoaded',function(){
97+
document.querySelectorAll('.hextra-asciinema-player').forEach(function(el){
98+
var opts={};
99+
if(el.dataset.theme) opts.theme=el.dataset.theme;
100+
if(el.dataset.speed) opts.speed=parseFloat(el.dataset.speed);
101+
if(el.dataset.autoplay==='true') opts.autoPlay=true;
102+
if(el.dataset.loop==='true') opts.loop=true;
103+
if(el.dataset.poster) opts.poster=el.dataset.poster;
104+
if(el.dataset.markers){try{opts.markers=JSON.parse(el.dataset.markers);}catch(e){}}
105+
AsciinemaPlayer.create(el.dataset.castFile,el,opts);
106+
});
107+
});
108+
</script>
109+
{{- end -}}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{{- /*
2+
Hextra badge shortcode (Bootstrap 5 port).
3+
4+
@param {string} content Badge text.
5+
@param {string} color Color name: gray, green, blue, yellow, red, purple, orange, indigo, amber (default: gray).
6+
@param {string} link Optional URL to wrap badge in a link.
7+
@param {string} icon Optional icon name.
8+
9+
Positional form: {{< hextra/badge "Badge text" >}}
10+
11+
@example {{< hextra/badge content="New" color="green" >}}
12+
*/ -}}
13+
14+
{{- /* Map Hextra / semantic color names to Bootstrap badge classes. */ -}}
15+
{{- $mapping := (dict
16+
"gray" "text-bg-secondary"
17+
"green" "text-bg-success"
18+
"blue" "text-bg-info"
19+
"yellow" "text-bg-warning"
20+
"red" "text-bg-danger"
21+
"purple" "text-bg-primary"
22+
"indigo" "text-bg-primary"
23+
"orange" "text-bg-warning"
24+
"amber" "text-bg-warning"
25+
"default" "text-bg-secondary"
26+
"tip" "text-bg-success"
27+
"info" "text-bg-info"
28+
"warning" "text-bg-warning"
29+
"error" "text-bg-danger"
30+
"important" "text-bg-primary"
31+
)
32+
-}}
33+
34+
{{- if .IsNamedParams -}}
35+
{{- $content := .Get "content" -}}
36+
{{- $color := .Get "color" | default (.Get "type") | default "gray" -}}
37+
{{- $link := .Get "link" | default "" -}}
38+
{{- $icon := .Get "icon" | default "" -}}
39+
40+
{{- $badgeClass := index $mapping $color | default "text-bg-secondary" -}}
41+
42+
{{- if $link -}}
43+
<a href="{{ $link }}" title="{{ $content | plainify }}" target="_blank" rel="noopener noreferrer" class="text-decoration-none">
44+
<span class="hextra-badge badge rounded-pill {{ $badgeClass }}">
45+
{{- with $icon }}{{ partial "hextra/utils/icon.html" (dict "name" . "attributes" `height="12" aria-hidden="true"`) }} {{ end -}}
46+
{{- $content -}}
47+
</span>
48+
</a>
49+
{{- else -}}
50+
<span class="hextra-badge badge rounded-pill {{ $badgeClass }}">
51+
{{- with $icon }}{{ partial "hextra/utils/icon.html" (dict "name" . "attributes" `height="12" aria-hidden="true"`) }} {{ end -}}
52+
{{- $content -}}
53+
</span>
54+
{{- end -}}
55+
56+
{{- else -}}
57+
{{- $content := .Get 0 -}}
58+
<span class="hextra-badge badge rounded-pill text-bg-secondary">
59+
{{- $content -}}
60+
</span>
61+
{{- end -}}

0 commit comments

Comments
 (0)