Init
This commit is contained in:
40
themes/hextra/layouts/404.html
Normal file
40
themes/hextra/layouts/404.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<body
|
||||
style='font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; height:100vh; text-align:center; display:flex; flex-direction:column; align-items:center; justify-content:center'
|
||||
>
|
||||
<div>
|
||||
<style>
|
||||
body {
|
||||
color: #000;
|
||||
background: #fff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hextra-error-h1 {
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
color: #fff;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.hextra-error-h1 {
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<h1
|
||||
class="hextra-error-h1"
|
||||
style='display: inline-block; margin: 0 20px 0 0; padding-right: 23px; font-size: 24px; font-weight: 500; vertical-align: top; line-height: 49px; font-feature-settings: "rlig" 1,"calt" 1,"ss01" 1,"ss06" 1 !important;'
|
||||
>
|
||||
404
|
||||
</h1>
|
||||
<div style="display: inline-block; text-align: left">
|
||||
<h2 style="font-size: 14px; font-weight: 400; line-height: 49px; margin: 0">This page could not be found.</h2>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
{{- if not (in (slice "note" "tip" "important" "warning" "caution") .AlertType) -}}
|
||||
{{- warnf "Alert type %s is not supported" .AlertType -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $content := .Text -}}
|
||||
{{- $alertType := .AlertType -}}
|
||||
{{- $alertTitle := .AlertTitle -}}
|
||||
|
||||
{{- partial "components/github-style-alert.html" (dict "content" $content "alertType" $alertType "alertTitle" $alertTitle) -}}
|
||||
@@ -0,0 +1,3 @@
|
||||
<blockquote>
|
||||
{{ .Text }}
|
||||
</blockquote>
|
||||
@@ -0,0 +1,4 @@
|
||||
<pre class="mermaid hx:mt-6">
|
||||
{{ .Inner | htmlEscape | safeHTML }}
|
||||
</pre>
|
||||
{{- .Page.Store.Set "hasMermaid" true -}}
|
||||
13
themes/hextra/layouts/_markup/render-codeblock.html
Normal file
13
themes/hextra/layouts/_markup/render-codeblock.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{- $class := .Attributes.class | default "" -}}
|
||||
{{- $filename := .Attributes.filename | default "" -}}
|
||||
{{- $base_url := .Attributes.base_url | default "" -}}
|
||||
{{- $lang := .Attributes.lang | default .Type -}}
|
||||
|
||||
|
||||
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">
|
||||
{{- partial "components/codeblock" (dict "filename" $filename "lang" $lang "base_url" $base_url "content" .Inner "options" .Options) -}}
|
||||
|
||||
{{- if or (eq site.Params.highlight.copy.enable nil) (site.Params.highlight.copy.enable) -}}
|
||||
{{- partialCached "components/codeblock-copy-button" (dict "filename" $filename) $filename -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
8
themes/hextra/layouts/_markup/render-heading.html
Normal file
8
themes/hextra/layouts/_markup/render-heading.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<h{{ .Level }} {{- with .Attributes.class }} class="{{ . }}" {{- end }}>
|
||||
{{- .Text | safeHTML -}}
|
||||
{{- if gt .Level 1 -}}
|
||||
<span class="hx:absolute hx:-mt-20" id="{{ .Anchor | safeURL }}"></span>
|
||||
<a href="#{{ .Anchor | safeURL }}" class="subheading-anchor" aria-label="Permalink for this section"></a>
|
||||
{{- end -}}
|
||||
</h{{ .Level }}>
|
||||
{{- /* Drop trailing newlines */ -}}
|
||||
43
themes/hextra/layouts/_markup/render-image.html
Normal file
43
themes/hextra/layouts/_markup/render-image.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{{- $alt := .PlainText | safeHTML -}}
|
||||
{{- $lazyLoading := .Page.Site.Params.enableImageLazyLoading | default true -}}
|
||||
{{- $dest := .Destination -}}
|
||||
{{- $url := urls.Parse $dest -}}
|
||||
|
||||
{{- $isLocal := not $url.Scheme -}}
|
||||
{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}}
|
||||
{{- $startsWithSlash := hasPrefix $dest "/" -}}
|
||||
{{- $startsWithRelative := hasPrefix $dest "../" -}}
|
||||
|
||||
{{- if and $dest $isLocal -}}
|
||||
{{- if $startsWithSlash -}}
|
||||
{{- with or (.PageInner.Resources.Get $url.Path) (resources.Get $url.Path) -}}
|
||||
{{/* Images under assets directory */}}
|
||||
{{- $query := cond $url.RawQuery (printf "?%s" $url.RawQuery) "" -}}
|
||||
{{- $fragment := cond $url.Fragment (printf "#%s" $url.Fragment) "" -}}
|
||||
{{- $dest = printf "%s%s%s" .RelPermalink $query $fragment -}}
|
||||
{{- else -}}
|
||||
{{/* Images under static directory */}}
|
||||
{{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}}
|
||||
{{- end -}}
|
||||
{{- else if and $isPage (not $startsWithRelative) -}}
|
||||
{{/* Images that are sibling to the individual page file */}}
|
||||
{{ $dest = (printf "../%s" $dest) }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $attributes := "" -}}
|
||||
{{- range $key, $value := .Attributes -}}
|
||||
{{- if $value -}}
|
||||
{{- $pair := printf "%s=%q" $key ($value | transform.HTMLEscape) -}}
|
||||
{{- $attributes = printf "%s %s" $attributes $pair -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- with .Title -}}
|
||||
<figure>
|
||||
<img src="{{ $dest | safeURL }}" title="{{ . }}" alt="{{ $alt }}" {{ $attributes | safeHTMLAttr }} {{ if $lazyLoading }}loading="lazy"{{ end }} />
|
||||
<figcaption>{{ . }}</figcaption>
|
||||
</figure>
|
||||
{{- else -}}
|
||||
<img src="{{ $dest | safeURL }}" alt="{{ $alt }}" {{ $attributes | safeHTMLAttr }} {{ if $lazyLoading }}loading="lazy"{{ end }} />
|
||||
{{- end -}}
|
||||
25
themes/hextra/layouts/_markup/render-link.html
Normal file
25
themes/hextra/layouts/_markup/render-link.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{{- $dest := .Destination -}}
|
||||
{{- $url := urls.Parse $dest -}}
|
||||
|
||||
{{- if and $dest (hasPrefix $dest "/") -}}
|
||||
{{- with or (.PageInner.GetPage $url.Path) (.PageInner.Resources.Get $url.Path) (resources.Get $url.Path) -}}
|
||||
{{- $query := cond $url.RawQuery (printf "?%s" $url.RawQuery) "" -}}
|
||||
{{- $fragment := cond $url.Fragment (printf "#%s" $url.Fragment) "" -}}
|
||||
{{- $dest = printf "%s%s%s" .RelPermalink $query $fragment -}}
|
||||
{{- else -}}
|
||||
{{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- with . -}}
|
||||
{{- $isExternal := strings.HasPrefix .Destination "http" -}}
|
||||
<a href="{{ $dest | safeURL }}"
|
||||
{{- with .Title -}}title="{{ . }}"{{- end -}}
|
||||
{{- if $isExternal -}}target="_blank" rel="noopener"{{- end -}}
|
||||
>
|
||||
{{- .Text | safeHTML -}}
|
||||
{{- if and .Page.Site.Params.externalLinkDecoration $isExternal -}}
|
||||
{{- partial "utils/icon.html" (dict "name" "arrow-up-right" "attributes" `class="hx:inline hx:rtl:rotate-270 hx:align-baseline" height="1em"`) -}}
|
||||
{{- end -}}
|
||||
</a>
|
||||
{{- end -}}
|
||||
20
themes/hextra/layouts/_markup/render-passthrough.html
Normal file
20
themes/hextra/layouts/_markup/render-passthrough.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{- $engine := site.Params.math.engine | default "katex" -}}
|
||||
{{- if eq $engine "katex" -}}
|
||||
{{- $opts := dict "output" "htmlAndMathml" "displayMode" (eq .Type "block") }}
|
||||
{{- with try (transform.ToMath .Inner $opts) }}
|
||||
{{- with .Err }}
|
||||
{{ errorf "Unable to render mathematical markup to HTML using the transform.ToMath function. The KaTeX display engine threw the following error: %s: see %s." . $.Position }}
|
||||
{{- else }}
|
||||
{{- .Value }}
|
||||
{{- $.Page.Store.Set "hasMath" true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else -}}
|
||||
{{/* MathJax - need to add delimiters back in */}}
|
||||
{{- $.Page.Store.Set "hasMath" true }}
|
||||
{{- if eq .Type "block" -}}
|
||||
\[{{- .Inner -}}\]
|
||||
{{- else -}}
|
||||
\( {{- .Inner -}} \)
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
18
themes/hextra/layouts/_partials/banner.html
Normal file
18
themes/hextra/layouts/_partials/banner.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{- if site.Params.banner }}
|
||||
<div class="hextra-banner hx:max-md:sticky hx:top-0 hx:z-20 hx:px-6 hx:text-center hx:text-slate-50 hx:dark:text-white hx:bg-neutral-900 hx:dark:bg-neutral-800 hx:print:[display:none]">
|
||||
<div class="hx:relative hx:flex hx:items-center hx:justify-center hx:font-medium hx:text-sm hx:py-2.5">
|
||||
{{- with partial "custom/banner.html" . -}}
|
||||
{{- . -}}
|
||||
{{- else -}}
|
||||
<div style="white-space: pre-wrap" class="hx:px-8">
|
||||
{{- site.Params.banner.message | default "🎉 Welcome! This is a banner message." | .RenderString -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
<button
|
||||
class="hextra-banner-close-button hx:cursor-pointer hx:absolute hx:right-0 hx:text-white hx:font-bold hx:leading-none hx:hover:opacity-75 hx:transition hx:w-10 hx:h-10 hx:-mr-2 hx:md:mr-0 hx:flex hx:items-center hx:justify-center"
|
||||
>
|
||||
{{- partial "utils/icon.html" (dict "name" "x" "attributes" "height=16") -}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
17
themes/hextra/layouts/_partials/breadcrumb.html
Normal file
17
themes/hextra/layouts/_partials/breadcrumb.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{- $page := .page -}}
|
||||
{{- $enable := .enable -}}
|
||||
{{- if (default $enable $page.Params.breadcrumbs) -}}
|
||||
<div class="hx:mt-1.5 hx:flex hx:items-center hx:gap-1 hx:overflow-hidden hx:text-sm hx:text-gray-500 hx:dark:text-gray-400 hx:contrast-more:text-current">
|
||||
{{- range $page.Ancestors.Reverse }}
|
||||
{{- if not .IsHome }}
|
||||
<div class="hx:whitespace-nowrap hx:transition-colors hx:min-w-[24px] hx:overflow-hidden hx:text-ellipsis hx:hover:text-gray-900 hx:dark:hover:text-gray-100">
|
||||
<a href="{{ .RelPermalink }}">{{- partial "utils/title" . -}}</a>
|
||||
</div>
|
||||
{{- partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"hx:w-3.5 hx:shrink-0 hx:rtl:-rotate-180\"") -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
<div class="hx:whitespace-nowrap hx:transition-colors hx:font-medium hx:text-gray-700 hx:contrast-more:font-bold hx:contrast-more:text-current hx:dark:text-gray-100 hx:contrast-more:dark:text-current">
|
||||
{{- partial "utils/title" $page -}}
|
||||
</div>
|
||||
</div>
|
||||
{{ end -}}
|
||||
@@ -0,0 +1,24 @@
|
||||
{{- if hugo.IsProduction -}}
|
||||
|
||||
<!-- Google Analytics -->
|
||||
{{- if .Site.Config.Services.GoogleAnalytics.ID }}
|
||||
<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin />
|
||||
{{ partial "google-analytics.html" . -}}
|
||||
{{- end }}
|
||||
|
||||
<!-- Umami -->
|
||||
{{- if .Site.Params.analytics.umami -}}
|
||||
{{ partial "components/analytics/umami.html" . }}
|
||||
{{- end }}
|
||||
|
||||
<!-- Matomo -->
|
||||
{{- if .Site.Params.analytics.matomo -}}
|
||||
{{ partial "components/analytics/matomo.html" . }}
|
||||
{{- end }}
|
||||
|
||||
<!-- GoatCounter -->
|
||||
{{- if .Site.Params.analytics.goatCounter -}}
|
||||
{{ partial "components/analytics/goat-counter.html" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{- end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{- with .Site.Params.analytics.goatCounter -}}
|
||||
{{- if not .code -}}
|
||||
{{- errorf "Missing GoatCounter 'code' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#goatcounter-analytics" -}}
|
||||
{{- end -}}
|
||||
|
||||
<script
|
||||
data-goatcounter="https://{{ .code }}.goatcounter.com/count"
|
||||
data-goatcounter-settings='
|
||||
{
|
||||
"no_onload":{{ .noOnload | default false }},
|
||||
"no_events":{{ .noEvents | default false }},
|
||||
"allow_local":{{ .allowLocal | default false }},
|
||||
"allow_frame":{{ .allowFrame | default false }}
|
||||
}
|
||||
'
|
||||
async src="//gc.zgo.at/count.js"></script>
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{- with site.Config.Services.GoogleAnalytics.ID }}
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ . }}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
|
||||
gtag("config", "{{ . }}");
|
||||
</script>
|
||||
{{ end -}}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{- /*
|
||||
Matomo Analytics.
|
||||
https://developer.matomo.org/guides/tracking-javascript-guide
|
||||
*/ -}}
|
||||
|
||||
{{- with .Site.Params.analytics.matomo -}}
|
||||
|
||||
{{- if not .serverURL }}
|
||||
{{- errorf "Missing Matomo 'serverURL' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#matomo-analytics" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if not .websiteID }}
|
||||
{{- errorf "Missing Matomo 'websiteID' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#matomo-analytics" -}}
|
||||
{{- end -}}
|
||||
|
||||
<!-- Matomo -->
|
||||
<script type="text/javascript">
|
||||
var _paq = window._paq = window._paq || [];
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="//{{ .serverURL }}/";
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', {{ .websiteID }}]);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.type='text/javascript'; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Matomo Code -->
|
||||
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,57 @@
|
||||
{{- /*
|
||||
Umami Analytics
|
||||
https://umami.is/docs/tracker-configuration
|
||||
*/ -}}
|
||||
|
||||
{{- with .Site.Params.analytics.umami -}}
|
||||
|
||||
{{- if not .serverURL }}
|
||||
{{- errorf "Missing Umami 'serverURL' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#umami-analytics" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if not .websiteID }}
|
||||
{{- errorf "Missing Umami 'websiteID' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#umami-analytics" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $attributes := newScratch -}}
|
||||
|
||||
{{- $attributes.SetInMap "umami" "src" (printf "%s/%s" .serverURL (.scriptName | default "script.js")) -}}
|
||||
{{- $attributes.SetInMap "umami" "data-website-id" .websiteID -}}
|
||||
|
||||
{{- if .hostURL -}}
|
||||
{{- /* https://umami.is/docs/tracker-configuration#data-host-url */ -}}
|
||||
{{- $attributes.SetInMap "umami" "data-host-url" .hostURL -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .autoTrack -}}
|
||||
{{- /* https://umami.is/docs/tracker-configuration#data-auto-track */ -}}
|
||||
{{- $attributes.SetInMap "umami" "data-auto-track" .autoTrack -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .tag -}}
|
||||
{{- /* https://umami.is/docs/tracker-configuration#data-tag */ -}}
|
||||
{{- $attributes.SetInMap "umami" "data-tag" .tag -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .excludeSearch -}}
|
||||
{{- /* https://umami.is/docs/tracker-configuration#data-exclude-search */ -}}
|
||||
{{- $attributes.SetInMap "umami" "data-exclude-search" .excludeSearch -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .excludeHash -}}
|
||||
{{- /* https://umami.is/docs/tracker-configuration#data-exclude-hash */ -}}
|
||||
{{- $attributes.SetInMap "umami" "data-exclude-hash" .excludeHash -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .doNotTrack -}}
|
||||
{{- /* https://umami.is/docs/tracker-configuration#data-do-not-track */ -}}
|
||||
{{- $attributes.SetInMap "umami" "data-do-not-track" .doNotTrack -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .domains -}}
|
||||
{{- /* https://umami.is/docs/tracker-configuration#data-domains */ -}}
|
||||
{{- $attributes.SetInMap "umami" "data-domains" .domains -}}
|
||||
{{- end -}}
|
||||
|
||||
<script async defer {{ range $k, $v := ($attributes.Get "umami" ) }} {{ (printf `%s=%q` $k $v) | safeHTMLAttr }}{{- end -}}></script>
|
||||
{{- end -}}
|
||||
39
themes/hextra/layouts/_partials/components/blog-pager.html
Normal file
39
themes/hextra/layouts/_partials/components/blog-pager.html
Normal file
@@ -0,0 +1,39 @@
|
||||
{{/*
|
||||
Blog pagination component for list pages (e.g., blog list, category list)
|
||||
|
||||
Usage: {{ partial "components/blog-pager.html" $paginator }}
|
||||
|
||||
Parameters:
|
||||
- . (context): Hugo paginator object
|
||||
*/}}
|
||||
|
||||
{{- $paginator := . -}}
|
||||
{{- $prevText := (T "previous") | default "Prev" -}}
|
||||
{{- $nextText := (T "next") | default "Next" -}}
|
||||
{{- $prevLabel := printf "%s %d/%d" $prevText (sub $paginator.PageNumber 1) $paginator.TotalPages -}}
|
||||
{{- $nextLabel := printf "%s %d/%d" $nextText (add $paginator.PageNumber 1) $paginator.TotalPages -}}
|
||||
|
||||
{{- if or $paginator.HasPrev $paginator.HasNext -}}
|
||||
<div class="hx:mb-8 hx:flex hx:items-center hx:border-t hx:pt-8 hx:border-gray-200 hx:dark:border-neutral-800 hx:contrast-more:border-neutral-400 hx:dark:contrast-more:border-neutral-400 hx:print:hidden">
|
||||
{{- if $paginator.HasPrev -}}
|
||||
<a
|
||||
href="{{ $paginator.Prev.URL }}"
|
||||
title="{{ $prevLabel }}"
|
||||
class="hx:flex hx:max-w-[50%] hx:items-center hx:gap-1 hx:py-4 hx:text-base hx:font-medium hx:text-gray-600 hx:transition-colors [word-break:break-word] hx:hover:text-primary-600 hx:dark:text-gray-300 hx:md:text-lg hx:ltr:pr-4 hx:rtl:pl-4"
|
||||
>
|
||||
{{- partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"hx:inline hx:h-5 hx:shrink-0 hx:ltr:rotate-180\"") -}}
|
||||
{{ $prevLabel }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
{{- if $paginator.HasNext -}}
|
||||
<a
|
||||
href="{{ $paginator.Next.URL }}"
|
||||
title="{{ $nextLabel }}"
|
||||
class="hx:flex hx:max-w-[50%] hx:items-center hx:gap-1 hx:py-4 hx:text-base hx:font-medium hx:text-gray-600 hx:transition-colors [word-break:break-word] hx:hover:text-primary-600 hx:dark:text-gray-300 hx:md:text-lg hx:ltr:ml-auto hx:ltr:pl-4 hx:ltr:text-right hx:rtl:mr-auto hx:rtl:pr-4 hx:rtl:text-left"
|
||||
>
|
||||
{{ $nextLabel }}
|
||||
{{- partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"hx:inline hx:h-5 hx:shrink-0 hx:rtl:-rotate-180\"") -}}
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,15 @@
|
||||
{{/* TODO: remove filename variable */}}
|
||||
{{- $filename := .filename | default "" -}}
|
||||
{{- $display := site.Params.highlight.copy.display | default "hover" -}}
|
||||
{{- $copyCode := (T "copyCode") | default "Copy code" -}}
|
||||
|
||||
|
||||
<div class="hextra-code-copy-btn-container {{ if eq $display `hover` }}hx:opacity-0{{ end }} hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 {{ if $filename }}hx:top-8{{ else }}hx:top-0{{ end }}">
|
||||
<button
|
||||
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
|
||||
title="{{ $copyCode }}"
|
||||
>
|
||||
<div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
|
||||
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
|
||||
</button>
|
||||
</div>
|
||||
29
themes/hextra/layouts/_partials/components/codeblock.html
Normal file
29
themes/hextra/layouts/_partials/components/codeblock.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{{ $filename := .filename | default "" -}}
|
||||
{{ $base_url := .base_url | default "" -}}
|
||||
{{ $lang := .lang | default "" }}
|
||||
{{ $content := .content }}
|
||||
{{ $options := .options | default (dict) }}
|
||||
|
||||
{{- if $filename -}}
|
||||
<div class="hextra-code-filename not-prose" dir="auto">
|
||||
{{- if $base_url -}}
|
||||
|
||||
{{- $base_url = strings.TrimSuffix "/" $base_url -}}
|
||||
{{- $filename = strings.TrimPrefix "/" $filename -}}
|
||||
{{- $file_url := urls.JoinPath $base_url $filename -}}
|
||||
|
||||
<a class="hx:no-underline hx:inline-flex hx:items-center hx:gap-1" href="{{ $file_url }}" target="_blank" rel="noopener noreferrer">
|
||||
<span>{{- $filename -}}</span>
|
||||
{{- partial "utils/icon" (dict "name" "external-link" "attributes" "height=1em") -}}
|
||||
</a>
|
||||
{{- else -}}
|
||||
{{- $filename -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
{{- if transform.CanHighlight $lang -}}
|
||||
<div>{{- highlight $content $lang $options -}}</div>
|
||||
{{- else -}}
|
||||
<div><pre><code>{{ $content }}</code></pre></div>
|
||||
{{- end -}}
|
||||
11
themes/hextra/layouts/_partials/components/comments.html
Normal file
11
themes/hextra/layouts/_partials/components/comments.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{{- $enableComments := site.Params.comments.enable | default false -}}
|
||||
|
||||
{{ if not (eq .Params.comments nil) }}
|
||||
{{ $enableComments = .Params.comments }}
|
||||
{{ end }}
|
||||
|
||||
{{- if $enableComments -}}
|
||||
{{- if eq site.Params.comments.type "giscus" -}}
|
||||
{{ partial "components/giscus.html" . }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
85
themes/hextra/layouts/_partials/components/giscus.html
Normal file
85
themes/hextra/layouts/_partials/components/giscus.html
Normal file
@@ -0,0 +1,85 @@
|
||||
{{- $lang := site.Language.Lang | default `en` -}}
|
||||
{{- if hasPrefix $lang "zh" -}}
|
||||
{{- /* See: https://github.com/giscus/giscus/tree/main/locales */}}
|
||||
{{- $lang = site.Language.LanguageCode | default `zh-CN` -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- with site.Params.comments.giscus -}}
|
||||
<script>
|
||||
function getGiscusTheme() {
|
||||
const giscusTheme = '{{ .theme }}';
|
||||
if (giscusTheme === 'light' || giscusTheme === 'dark') {
|
||||
return giscusTheme;
|
||||
}
|
||||
|
||||
const hugoTheme = localStorage.getItem("color-theme");
|
||||
if (hugoTheme === 'light' || hugoTheme === 'dark') {
|
||||
return hugoTheme;
|
||||
}
|
||||
|
||||
if (hugoTheme === 'system') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
const defaultTheme = '{{ site.Params.theme.default }}';
|
||||
if (defaultTheme === 'light' || defaultTheme === 'dark') {
|
||||
return defaultTheme;
|
||||
}
|
||||
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function setGiscusTheme() {
|
||||
const iframe = document.querySelector('iframe.giscus-frame');
|
||||
if (!iframe) return;
|
||||
|
||||
const msg = {
|
||||
giscus: {
|
||||
setConfig: {
|
||||
theme: getGiscusTheme(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
iframe.contentWindow.postMessage(msg, 'https://giscus.app');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const giscusAttributes = {
|
||||
"src": "https://giscus.app/client.js",
|
||||
"data-repo": "{{ .repo }}",
|
||||
"data-repo-id": "{{ .repoId }}",
|
||||
"data-category": "{{ .category }}",
|
||||
"data-category-id": "{{ .categoryId }}",
|
||||
"data-mapping": "{{ .mapping | default `pathname` }}",
|
||||
"data-strict": "{{ (string .strict) | default 0 }}",
|
||||
"data-reactions-enabled": "{{ (string .reactionsEnabled) | default 1 }}",
|
||||
"data-emit-metadata": "{{ (string .emitMetadata) | default 0 }}",
|
||||
"data-input-position": "{{ .inputPosition | default `top` }}",
|
||||
"data-theme": getGiscusTheme(),
|
||||
"data-lang": "{{ .lang | default $lang }}",
|
||||
"crossorigin": "anonymous",
|
||||
"async": "",
|
||||
};
|
||||
|
||||
// Dynamically create script tag
|
||||
const giscusScript = document.createElement("script");
|
||||
Object.entries(giscusAttributes).forEach(([key, value]) => giscusScript.setAttribute(key, value));
|
||||
// Random hash id to avoid conflicts with titles inside pages.
|
||||
document.getElementById('giscus-hextra-bb112b9f807c37c1752e5da6a1652a29').appendChild(giscusScript);
|
||||
|
||||
// Listen for system theme changes
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", setGiscusTheme);
|
||||
|
||||
// Update giscus theme when theme switcher is clicked
|
||||
const themeToggleOptions = document.querySelectorAll(".hextra-theme-toggle-options p");
|
||||
if (themeToggleOptions) {
|
||||
themeToggleOptions.forEach(toggle => toggle.addEventListener('click', setGiscusTheme));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="giscus-hextra-bb112b9f807c37c1752e5da6a1652a29"></div>
|
||||
{{- else -}}
|
||||
{{ warnf "giscus is not configured" }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,53 @@
|
||||
{{- $content := .content -}}
|
||||
{{- $alertType := .alertType -}}
|
||||
{{- $alertTitle := .alertTitle -}}
|
||||
|
||||
{{- $styles := newScratch -}}
|
||||
{{- $styles.Set "default" (dict
|
||||
"icon" "light-bulb"
|
||||
"style" "hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "note" (dict
|
||||
"icon" "information-circle"
|
||||
"style" "hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "tip" (dict
|
||||
"icon" "light-bulb"
|
||||
"style" "hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "important" (dict
|
||||
"icon" "information-circle"
|
||||
"style" "hx:border-purple-200 hx:bg-purple-100 hx:text-purple-900 hx:dark:border-purple-200/30 hx:dark:bg-purple-900/30 hx:dark:text-purple-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "warning" (dict
|
||||
"icon" "exclamation"
|
||||
"style" "hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "caution" (dict
|
||||
"icon" "exclamation-circle"
|
||||
"style" "hx:border-red-200 hx:bg-red-100 hx:text-red-900 hx:dark:border-red-200/30 hx:dark:bg-red-900/30 hx:dark:text-red-200"
|
||||
)
|
||||
-}}
|
||||
|
||||
{{- $style := or ($styles.Get $alertType) ($styles.Get "default") -}}
|
||||
{{- $title := or $alertTitle (or (i18n $alertType) (title $alertType)) -}}
|
||||
|
||||
<div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current {{ $style.style }}">
|
||||
<p class="hx:flex hx:items-center hx:font-medium">
|
||||
{{- with $style.icon -}}
|
||||
{{- partial "utils/icon.html" (dict "name" . "attributes" `height=16px class="hx:inline-block hx:align-middle hx:mr-2"`) -}}
|
||||
{{- end -}}
|
||||
{{- $title -}}
|
||||
</p>
|
||||
|
||||
<div class="hx:w-full hx:min-w-0 hx:leading-7">
|
||||
<div class="hx:mt-6 hx:leading-7 hx:first:mt-0">
|
||||
{{- $content -}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
12
themes/hextra/layouts/_partials/components/last-updated.html
Normal file
12
themes/hextra/layouts/_partials/components/last-updated.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{{- $lastUpdated := (T "lastUpdated") | default "Last updated on" -}}
|
||||
|
||||
{{- if site.Params.displayUpdatedDate -}}
|
||||
{{- with .Lastmod -}}
|
||||
{{ $datetime := (time.Format "2006-01-02T15:04:05.000Z" .) }}
|
||||
<div class="hx:mt-12 hx:mb-8 hx:block hx:text-xs hx:text-gray-500 hx:ltr:text-right hx:rtl:text-left hx:dark:text-gray-400">{{ $lastUpdated }} <time datetime="{{ $datetime }}">{{ partial "utils/format-date" . }}</time></div>
|
||||
{{- else -}}
|
||||
<div class="hx:mt-16"></div>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<div class="hx:mt-16"></div>
|
||||
{{- end -}}
|
||||
53
themes/hextra/layouts/_partials/components/pager.html
Normal file
53
themes/hextra/layouts/_partials/components/pager.html
Normal file
@@ -0,0 +1,53 @@
|
||||
{{/* Article navigation on the footer of the article */}}
|
||||
|
||||
{{- $reversePagination := .Store.Get "reversePagination" | default false -}}
|
||||
|
||||
{{- $prev := cond $reversePagination .PrevInSection .NextInSection -}}
|
||||
{{- $next := cond $reversePagination .NextInSection .PrevInSection -}}
|
||||
|
||||
{{- if eq .Params.prev false }}
|
||||
{{- if $reversePagination }}{{ $next = false }}{{ else }}{{ $prev = false }}{{ end -}}
|
||||
{{ else }}
|
||||
{{- with .Params.prev -}}
|
||||
{{- with $.Site.GetPage . -}}
|
||||
{{- if $reversePagination }}{{ $next = . }}{{ else }}{{ $prev = . }}{{ end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq .Params.next false }}
|
||||
{{- if $reversePagination }}{{ $prev = false }}{{ else }}{{ $next = false }}{{ end -}}
|
||||
{{ else }}
|
||||
{{- with .Params.next -}}
|
||||
{{- with $.Site.GetPage . -}}
|
||||
{{- if $reversePagination }}{{ $prev = . }}{{ else }}{{ $next = . }}{{ end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if or $prev $next -}}
|
||||
<div class="hx:mb-8 hx:flex hx:items-center hx:border-t hx:pt-8 hx:border-gray-200 hx:dark:border-neutral-800 hx:contrast-more:border-neutral-400 hx:dark:contrast-more:border-neutral-400 hx:print:hidden">
|
||||
{{- if $prev -}}
|
||||
{{- $linkTitle := partial "utils/title" $prev -}}
|
||||
<a
|
||||
href="{{ $prev.RelPermalink }}"
|
||||
title="{{ $linkTitle }}"
|
||||
class="hx:flex hx:max-w-[50%] hx:items-center hx:gap-1 hx:py-4 hx:text-base hx:font-medium hx:text-gray-600 hx:transition-colors [word-break:break-word] hx:hover:text-primary-600 hx:dark:text-gray-300 hx:md:text-lg hx:ltr:pr-4 hx:rtl:pl-4"
|
||||
>
|
||||
{{- partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"hx:inline hx:h-5 hx:shrink-0 hx:ltr:rotate-180\"") -}}
|
||||
{{- $linkTitle -}}
|
||||
</a>
|
||||
{{- end -}}
|
||||
{{- if $next -}}
|
||||
{{- $linkTitle := partial "utils/title" $next -}}
|
||||
<a
|
||||
href="{{ $next.RelPermalink }}"
|
||||
title="{{ $linkTitle }}"
|
||||
class="hx:flex hx:max-w-[50%] hx:items-center hx:gap-1 hx:py-4 hx:text-base hx:font-medium hx:text-gray-600 hx:transition-colors [word-break:break-word] hx:hover:text-primary-600 hx:dark:text-gray-300 hx:md:text-lg hx:ltr:ml-auto hx:ltr:pl-4 hx:ltr:text-right hx:rtl:mr-auto hx:rtl:pr-4 hx:rtl:text-left"
|
||||
>
|
||||
{{- $linkTitle -}}
|
||||
{{- partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"hx:inline hx:h-5 hx:shrink-0 hx:rtl:-rotate-180\"") -}}
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
0
themes/hextra/layouts/_partials/custom/banner.html
Normal file
0
themes/hextra/layouts/_partials/custom/banner.html
Normal file
0
themes/hextra/layouts/_partials/custom/footer.html
Normal file
0
themes/hextra/layouts/_partials/custom/footer.html
Normal file
6
themes/hextra/layouts/_partials/favicons.html
Normal file
6
themes/hextra/layouts/_partials/favicons.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<link rel="icon shortcut" href="{{ "favicon.ico" | relURL }}" sizes="32x32" />
|
||||
<link rel="icon" href="{{ "favicon.svg" | relURL }}" type="image/svg+xml" id="favicon-svg" />
|
||||
<link rel="icon" href="{{ "favicon-16x16.png" | relURL }}" type="image/png" sizes="16x16" />
|
||||
<link rel="icon" href="{{ "favicon-32x32.png" | relURL }}" type="image/png" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" href="{{ "apple-touch-icon.png" | relURL }}" sizes="180x180" />
|
||||
<link fetchpriority="low" href="{{ "site.webmanifest" | relURL }}" rel="manifest" />
|
||||
44
themes/hextra/layouts/_partials/footer.html
Normal file
44
themes/hextra/layouts/_partials/footer.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{{- $enableFooterSwitches := .Store.Get "enableFooterSwitches" | default false -}}
|
||||
{{- $displayThemeToggle := site.Params.theme.displayToggle | default true -}}
|
||||
{{- $footerSwitchesVisible := and $enableFooterSwitches (or hugo.IsMultilingual $displayThemeToggle) -}}
|
||||
{{- $copyrightSectionVisible := or (.Site.Params.footer.displayPoweredBy | default true) .Site.Params.footer.displayCopyright -}}
|
||||
|
||||
{{- $copyright := (T "copyright") | default "© 2024 Hextra." -}}
|
||||
{{- $poweredBy := (T "poweredBy") | default "Powered by Hextra" -}}
|
||||
|
||||
|
||||
<footer class="hextra-footer hx:bg-gray-100 hx:pb-[env(safe-area-inset-bottom)] hx:dark:bg-neutral-900 hx:print:bg-transparent">
|
||||
{{- if $footerSwitchesVisible -}}
|
||||
<div class="hx:mx-auto hx:flex hx:gap-2 hx:py-2 hx:px-4 hextra-max-footer-width">
|
||||
{{- partial "language-switch.html" (dict "context" .) -}}
|
||||
{{- with $displayThemeToggle }}{{ partial "theme-toggle.html" }}{{ end -}}
|
||||
</div>
|
||||
{{- if or hugo.IsMultilingual $displayThemeToggle -}}
|
||||
<hr class="hx:border-gray-200 hx:dark:border-neutral-800" />
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<div class="hextra-custom-footer hextra-max-footer-width hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-right),1.5rem)] hx:text-gray-600 hx:dark:text-gray-400">
|
||||
{{- partial "custom/footer.html" (dict "context" . "switchesVisible" $footerSwitchesVisible "copyrightVisible" $copyrightSectionVisible) -}}
|
||||
</div>
|
||||
{{- if $copyrightSectionVisible -}}
|
||||
<div
|
||||
class="hextra-max-footer-width hx:mx-auto hx:flex hx:justify-center hx:py-12 hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-right),1.5rem)] hx:text-gray-600 hx:dark:text-gray-400 hx:md:justify-start"
|
||||
>
|
||||
<div class="hx:flex hx:w-full hx:flex-col hx:items-center hx:sm:items-start">
|
||||
{{- if (.Site.Params.footer.displayPoweredBy | default true) }}<div class="hx:font-semibold">{{ template "theme-credit" $poweredBy }}</div>{{- end -}}
|
||||
{{- if .Site.Params.footer.displayCopyright }}<div class="hx:mt-6 hx:text-xs">{{ $copyright | markdownify }}</div>{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
</footer>
|
||||
|
||||
{{- define "theme-credit" -}}
|
||||
<a class="hx:flex hx:text-sm hx:items-center hx:gap-1 hx:text-current" target="_blank" rel="noopener noreferrer" title="Hextra GitHub Homepage" href="https://github.com/imfing/hextra">
|
||||
<span>
|
||||
{{- . | markdownify -}}
|
||||
{{- if strings.Contains . "Hextra" -}}
|
||||
{{- partial "utils/icon.html" (dict "name" "hextra" "attributes" `height=1em class="hx:inline-block hx:ltr:ml-1 hx:rtl:mr-1 hx:align-[-2.5px]"`) -}}
|
||||
{{- end -}}
|
||||
</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
2
themes/hextra/layouts/_partials/google-analytics.html
Normal file
2
themes/hextra/layouts/_partials/google-analytics.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{{- /* Only for compatibility. */ -}}
|
||||
{{- partial "components/analytics/google-analytics.html" . -}}
|
||||
81
themes/hextra/layouts/_partials/head.html
Normal file
81
themes/hextra/layouts/_partials/head.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
{{- $noindex := .Params.noindex | default false -}}
|
||||
{{ if and (hugo.IsProduction) (not $noindex) -}}
|
||||
<meta name="robots" content="index, follow" />
|
||||
{{ else -}}
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
{{ end -}}
|
||||
{{ partialCached "favicons.html" . -}}
|
||||
<title>
|
||||
{{- if .IsHome -}}
|
||||
{{ .Site.Title -}}
|
||||
{{ else -}}
|
||||
{{ with .Title }}{{ . }} – {{ end -}}
|
||||
{{ .Site.Title -}}
|
||||
{{ end -}}
|
||||
</title>
|
||||
<meta name="description" content="{{ partial "utils/page-description.html" . }}" />
|
||||
|
||||
{{- with .Params.canonical -}}
|
||||
<link rel="canonical" href="{{ . }}" itemprop="url" />
|
||||
{{- else -}}
|
||||
<link rel="canonical" href="{{ .Permalink }}" itemprop="url" />
|
||||
{{- end -}}
|
||||
|
||||
{{- partial "opengraph.html" . -}}
|
||||
{{- partial "schema.html" . -}}
|
||||
{{- partial "twitter_cards.html" . -}}
|
||||
|
||||
{{- $mainCss := resources.Get "css/compiled/main.css" -}}
|
||||
{{- $customCss := resources.Get "css/custom.css" -}}
|
||||
{{- $variablesCss := resources.Get "css/variables.css" | resources.ExecuteAsTemplate "css/variables.css" . -}}
|
||||
|
||||
{{- /* Production build */ -}}
|
||||
{{- if hugo.IsProduction }}
|
||||
{{- $styles := slice $variablesCss $mainCss $customCss | resources.Concat "css/compiled/main.css" | minify | fingerprint }}
|
||||
<link rel="preload" href="{{ $styles.RelPermalink }}" as="style" integrity="{{ $styles.Data.Integrity }}" />
|
||||
<link href="{{ $styles.RelPermalink }}" rel="stylesheet" integrity="{{ $styles.Data.Integrity }}" />
|
||||
|
||||
{{- /* Theme development mode (non-production + theme environment) */ -}}
|
||||
{{- else if eq hugo.Environment "theme" }}
|
||||
{{- $devStyles := resources.Get "css/styles.css" | postCSS (dict "inlineImports" true) }}
|
||||
<link href="{{ $devStyles.RelPermalink }}" rel="stylesheet" />
|
||||
<link href="{{ $variablesCss.RelPermalink }}" rel="stylesheet" />
|
||||
<link href="{{ $customCss.RelPermalink }}" rel="stylesheet" />
|
||||
|
||||
{{- /* User local development */ -}}
|
||||
{{- else }}
|
||||
{{- $styles := resources.Get "css/compiled/main.css" -}}
|
||||
<link href="{{ $styles.RelPermalink }}" rel="stylesheet" />
|
||||
<link href="{{ $variablesCss.RelPermalink }}" rel="stylesheet" />
|
||||
<link href="{{ $customCss.RelPermalink }}" rel="stylesheet" />
|
||||
{{- end }}
|
||||
|
||||
{{ partial "components/analytics/analytics.html" . }}
|
||||
|
||||
{{- $scriptsHead := slice -}}
|
||||
{{- range resources.Match "js/head/*.js" -}}
|
||||
{{ $scriptsHead = $scriptsHead | append (resources.ExecuteAsTemplate .Name $ .) }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $scripts := $scriptsHead | resources.Concat "js/main-head.js" -}}
|
||||
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- $scripts = $scripts | minify | fingerprint -}}
|
||||
{{- end -}}
|
||||
<script src="{{ $scripts.RelPermalink }}" integrity="{{ $scripts.Data.Integrity }}"></script>
|
||||
|
||||
<!-- Math engine -->
|
||||
{{ $noop := .WordCount -}}
|
||||
{{- $engine := site.Params.math.engine | default "katex" -}}
|
||||
{{ if and (.Page.Store.Get "hasMath") (eq $engine "katex") -}}
|
||||
{{ partialCached "scripts/katex.html" . -}}
|
||||
{{ else if and (.Page.Store.Get "hasMath") (eq $engine "mathjax") -}}
|
||||
{{ partialCached "scripts/mathjax.html" . -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ partial "utils/page-width-override.html" . }}
|
||||
{{ partial "custom/head-end.html" . -}}
|
||||
</head>
|
||||
50
themes/hextra/layouts/_partials/language-switch.html
Normal file
50
themes/hextra/layouts/_partials/language-switch.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{{- $page := .context -}}
|
||||
{{- $iconName := .iconName | default "globe-alt" -}}
|
||||
{{- $iconHeight := .iconHeight | default 12 -}}
|
||||
{{- $location := .location -}}
|
||||
|
||||
{{- $class := .class | default "hx:h-7 hx:px-2 hx:text-xs hx:text-gray-600 hx:transition-colors hx:dark:text-gray-400 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50" -}}
|
||||
|
||||
{{- $grow := .grow -}}
|
||||
{{- $hideLabel := .hideLabel | default false -}}
|
||||
|
||||
{{- $changeLanguage := (T "changeLanguage") | default "Change language" -}}
|
||||
|
||||
{{- if hugo.IsMultilingual -}}
|
||||
<div class="hx:flex hx:justify-items-start {{ if $grow }}hx:grow{{ end }}">
|
||||
<button
|
||||
title="{{ $changeLanguage }}"
|
||||
data-state="closed"
|
||||
data-location="{{ $location }}"
|
||||
class="hextra-language-switcher hx:cursor-pointer hx:rounded-md hx:text-left hx:font-medium {{ $class }} hx:grow"
|
||||
type="button"
|
||||
aria-label="{{ $changeLanguage }}"
|
||||
>
|
||||
<div class="hx:flex hx:items-center hx:gap-2 hx:capitalize">
|
||||
{{- partial "utils/icon" (dict "name" $iconName "attributes" (printf "height=%d" $iconHeight)) -}}
|
||||
{{- if not $hideLabel }}<span>{{ site.Language.LanguageName }}</span>{{ end -}}
|
||||
</div>
|
||||
</button>
|
||||
<ul
|
||||
class="hextra-language-options hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-md hx:ring-1 hx:ring-black/5 hx:bg-white hx:py-1 hx:text-sm hx:shadow-lg hx:dark:ring-white/20 hx:dark:bg-neutral-800"
|
||||
style="position: fixed; inset: auto auto 0px 0px; margin: 0px; min-width: 100px;"
|
||||
>
|
||||
{{ range site.Languages }}
|
||||
{{ $link := partial "utils/lang-link" (dict "lang" .Lang "context" $page) }}
|
||||
<li class="hx:flex hx:flex-col">
|
||||
<a
|
||||
href="{{ $link }}"
|
||||
class="hx:text-gray-800 hx:dark:text-gray-100 hx:hover:bg-primary-50 hx:hover:text-primary-600 hx:hover:dark:bg-primary-500/10 hx:hover:dark:text-primary-600 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9"
|
||||
>
|
||||
{{- .LanguageName -}}
|
||||
{{- if eq .LanguageName site.Language.LanguageName -}}
|
||||
<span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3">
|
||||
{{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}}
|
||||
</span>
|
||||
{{- end -}}
|
||||
</a>
|
||||
</li>
|
||||
{{ end -}}
|
||||
</ul>
|
||||
</div>
|
||||
{{- end -}}
|
||||
76
themes/hextra/layouts/_partials/navbar-link.html
Normal file
76
themes/hextra/layouts/_partials/navbar-link.html
Normal file
@@ -0,0 +1,76 @@
|
||||
{{- $currentPage := .currentPage -}}
|
||||
{{- $link := .link -}}
|
||||
{{- $item := .item -}}
|
||||
{{- $icon := .icon -}}
|
||||
{{- $external := .external -}}
|
||||
|
||||
{{- $active := or ($currentPage.HasMenuCurrent "main" $item) ($currentPage.IsMenuCurrent "main" $item) -}}
|
||||
{{- $activeClass := cond $active "hx:font-medium" "hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200" -}}
|
||||
|
||||
{{- if $item.HasChildren -}}
|
||||
{{- /* Dropdown menu for items with children */ -}}
|
||||
<div class="hx:relative hx:hidden hx:md:inline-block">
|
||||
<button
|
||||
title="{{ or (T $item.Identifier) $item.Name | safeHTML }}"
|
||||
data-state="closed"
|
||||
class="hextra-nav-menu-toggle hx:cursor-pointer hx:text-sm hx:contrast-more:text-gray-700 hx:contrast-more:dark:text-gray-100 hx:relative hx:-ml-2 hx:whitespace-nowrap hx:p-2 hx:flex hx:items-center hx:gap-1 {{ $activeClass }}"
|
||||
type="button"
|
||||
aria-label="{{ or (T $item.Identifier) $item.Name | safeHTML }}"
|
||||
>
|
||||
{{- if $icon -}}
|
||||
<span class="hx:inline-flex hx:items-center">
|
||||
{{- partial "utils/icon" (dict "name" $icon "attributes" `height="1em" class="hx:inline-block"`) -}}
|
||||
</span>
|
||||
{{- end -}}
|
||||
<span class="hx:text-center">
|
||||
{{- or (T $item.Identifier) $item.Name | safeHTML -}}
|
||||
</span>
|
||||
{{- partial "utils/icon.html" (dict "name" "chevron-down" "attributes" "height=12 class=\"hx:transition-transform hx:duration-200 hx:ease-in-out\"") -}}
|
||||
</button>
|
||||
<ul
|
||||
class="hextra-nav-menu-items hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-md hx:ring-1 hx:ring-black/5 hx:bg-white hx:py-1 hx:text-sm hx:shadow-lg hx:dark:ring-white/20 hx:dark:bg-neutral-800"
|
||||
style="min-width: 100px;"
|
||||
>
|
||||
{{ range $item.Children }}
|
||||
{{- $link := .URL -}}
|
||||
{{- $external := strings.HasPrefix $link "http" -}}
|
||||
{{- with .PageRef -}}
|
||||
{{- if hasPrefix . "/" -}}
|
||||
{{- $link = relLangURL (strings.TrimPrefix "/" .) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<li class="hextra-nav-menu-item hx:flex hx:flex-col">
|
||||
<a
|
||||
href="{{ $link }}"
|
||||
{{ if $external }}target="_blank" rel="noreferrer"{{ end }}
|
||||
class="hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9 hx:flex hx:items-center hx:gap-1"
|
||||
>
|
||||
{{- if and (eq .Params.type "link") .Params.icon -}}
|
||||
<span class="hx:inline-flex hx:items-center">
|
||||
{{- partial "utils/icon" (dict "name" .Params.icon "attributes" `height="1em" class="hx:inline-block"`) -}}
|
||||
</span>
|
||||
{{- end -}}
|
||||
{{- or (T .Identifier) .Name | safeHTML -}}
|
||||
</a>
|
||||
</li>
|
||||
{{- end -}}
|
||||
</ul>
|
||||
</div>
|
||||
{{- else -}}
|
||||
{{- /* Regular menu item without children */ -}}
|
||||
<a
|
||||
title="{{ or (T .Identifier) .Name | safeHTML }}"
|
||||
href="{{ $link }}"
|
||||
{{ if $external }}target="_blank" rel="noreferrer"{{ end }}
|
||||
class="hx:text-sm hx:contrast-more:text-gray-700 hx:contrast-more:dark:text-gray-100 hx:relative hx:-ml-2 hx:hidden hx:whitespace-nowrap hx:p-2 hx:md:inline-flex hx:items-center hx:gap-1 {{ $activeClass }}"
|
||||
>
|
||||
{{- if $icon -}}
|
||||
<span class="hx:inline-flex hx:items-center">
|
||||
{{- partial "utils/icon" (dict "name" $icon "attributes" `height="1em" class="hx:inline-block"`) -}}
|
||||
</span>
|
||||
{{- end -}}
|
||||
<span class="hx:text-center">
|
||||
{{- or (T $item.Identifier) $item.Name | safeHTML -}}
|
||||
</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
16
themes/hextra/layouts/_partials/navbar-title.html
Normal file
16
themes/hextra/layouts/_partials/navbar-title.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{{- $logoPath := .Site.Params.navbar.logo.path | default "images/logo.svg" -}}
|
||||
{{- $logoLink := .Site.Params.navbar.logo.link | default .Site.Home.RelPermalink -}}
|
||||
{{- $logoWidth := .Site.Params.navbar.logo.width | default "20" -}}
|
||||
{{- $logoHeight := .Site.Params.navbar.logo.height | default "20" -}}
|
||||
{{- $logoDarkPath := .Site.Params.navbar.logo.dark | default $logoPath -}}
|
||||
|
||||
<a class="hx:flex hx:items-center hx:hover:opacity-75 hx:ltr:mr-auto hx:rtl:ml-auto" href="{{ $logoLink }}">
|
||||
{{- $displayTitle := (.Site.Params.navbar.displayTitle | default true) }}
|
||||
{{- if (.Site.Params.navbar.displayLogo | default true) }}
|
||||
<img class="hx:mr-2 hx:block hx:dark:hidden" src="{{ $logoPath | relURL }}" alt="{{ cond $displayTitle `Logo` .Site.Title }}" height="{{ $logoHeight }}" width="{{ $logoWidth }}" />
|
||||
<img class="hx:mr-2 hx:hidden hx:dark:block" src="{{ $logoDarkPath | relURL }}" alt="{{ cond $displayTitle `Dark Logo` .Site.Title }}" height="{{ $logoHeight }}" width="{{ $logoWidth }}" />
|
||||
{{- end }}
|
||||
{{- if $displayTitle }}
|
||||
<span class="hx:mr-2 hx:font-extrabold hx:inline hx:select-none" title="{{ .Site.Title }}">{{- .Site.Title -}}</span>
|
||||
{{- end }}
|
||||
</a>
|
||||
59
themes/hextra/layouts/_partials/navbar.html
Normal file
59
themes/hextra/layouts/_partials/navbar.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{{- $navWidth := "hx:max-w-[90rem]" -}}
|
||||
{{- with .Site.Params.navbar.width -}}
|
||||
{{ if eq . "normal" -}}
|
||||
{{ $navWidth = "hx:max-w-screen-xl" -}}
|
||||
{{ else if eq . "full" -}}
|
||||
{{ $navWidth = "max-w-full" -}}
|
||||
{{ end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $page := . -}}
|
||||
{{- $iconHeight := 24 -}}
|
||||
|
||||
<div class="hextra-nav-container hx:sticky hx:top-0 hx:z-20 hx:w-full hx:bg-transparent hx:print:hidden">
|
||||
<div
|
||||
class="hextra-nav-container-blur hx:pointer-events-none hx:absolute hx:z-[-1] hx:h-full hx:w-full hx:bg-white hx:dark:bg-dark hx:shadow-[0_2px_4px_rgba(0,0,0,.02),0_1px_0_rgba(0,0,0,.06)] hx:contrast-more:shadow-[0_0_0_1px_#000] hx:dark:shadow-[0_-1px_0_rgba(255,255,255,.1)_inset] hx:contrast-more:dark:shadow-[0_0_0_1px_#fff]"
|
||||
></div>
|
||||
|
||||
<nav class="hextra-max-navbar-width hx:mx-auto hx:flex hx:items-center hx:justify-end hx:gap-2 hx:h-16 hx:px-6">
|
||||
{{ partial "navbar-title.html" . }}
|
||||
|
||||
{{- $currentPage := . -}}
|
||||
{{- range .Site.Menus.main -}}
|
||||
{{- if eq .Params.type "search" -}}
|
||||
{{- partial "search.html" (dict "params" .Params) -}}
|
||||
{{- else -}}
|
||||
{{- $link := .URL -}}
|
||||
{{- $external := strings.HasPrefix $link "http" -}}
|
||||
{{- with .PageRef -}}
|
||||
{{- if hasPrefix . "/" -}}
|
||||
{{- $link = relLangURL (strings.TrimPrefix "/" .) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq .Params.type "link" -}}
|
||||
{{- partial "navbar-link.html" (dict "currentPage" $currentPage "link" $link "external" $external "item" . "icon" .Params.icon) -}}
|
||||
{{- else if eq .Params.type "theme-toggle" -}}
|
||||
{{- partial "theme-toggle.html" (dict "iconHeight" $iconHeight "hideLabel" (not .Params.label) "iconHeight" $iconHeight "location" "top" "class" "hx:p-2") -}}
|
||||
{{- else if eq .Params.type "language-switch" -}}
|
||||
{{- partial "language-switch" (dict "context" $page "grow" false "hideLabel" (not .Params.label) "iconName" (.Params.icon | default "translate") "iconHeight" $iconHeight "location" "top" "class" "hx:p-2") -}}
|
||||
{{- else if .Params.icon -}}
|
||||
{{- /* Display icon menu item */ -}}
|
||||
{{- if not $link -}}{{ warnf "Icon menu item '%s' has no URL" .Name }}{{- end -}}
|
||||
{{- $rel := cond (eq .Params.icon "mastodon") "noreferrer me" "noreferrer" }}
|
||||
<a class="hx:p-2 hx:text-current" {{ if $external }}target="_blank" rel="{{ $rel }}"{{ end }} href="{{ $link }}" title="{{ or (T .Identifier) .Name | safeHTML }}">
|
||||
{{- partial "utils/icon.html" (dict "name" .Params.icon "attributes" (printf "height=%d" $iconHeight)) -}}
|
||||
<span class="hx:sr-only">{{ or (T .Identifier) .Name | safeHTML }}</span>
|
||||
</a>
|
||||
{{- else -}}
|
||||
{{- partial "navbar-link.html" (dict "currentPage" $currentPage "link" $link "external" $external "item" .) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<button type="button" aria-label="Menu" class="hextra-hamburger-menu hx:cursor-pointer hx:-mr-2 hx:rounded-sm hx:p-2 hx:active:bg-gray-400/20 hx:md:hidden">
|
||||
{{- partial "utils/icon.html" (dict "name" "hamburger-menu" "attributes" (printf "height=%d" $iconHeight)) -}}
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
88
themes/hextra/layouts/_partials/opengraph.html
Normal file
88
themes/hextra/layouts/_partials/opengraph.html
Normal file
@@ -0,0 +1,88 @@
|
||||
{{/* Adapted from https://github.com/gohugoio/hugo/blob/v0.149.0/docs/layouts/_partials/opengraph/opengraph.html */}}
|
||||
|
||||
<meta property="og:title" content="{{ .Title }}">
|
||||
<meta
|
||||
property="og:description"
|
||||
content="{{ with .Description }}
|
||||
{{ . }}
|
||||
{{ else }}
|
||||
{{ if .IsPage }}
|
||||
{{ .Summary }}
|
||||
{{ else }}
|
||||
{{ with .Site.Params.description }}{{ . }}{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}">
|
||||
<meta
|
||||
property="og:type"
|
||||
content="{{ if .IsPage }}
|
||||
article
|
||||
{{ else }}
|
||||
website
|
||||
{{ end }}">
|
||||
<meta property="og:url" content="{{ .Permalink }}">
|
||||
|
||||
{{- with $.Params.images -}}
|
||||
{{- range first 6 . }}
|
||||
{{- with $.Resources.GetMatch . }}
|
||||
<!-- If the string matches a page resource, use that -->
|
||||
<meta property="og:image" content="{{ .Permalink }}">
|
||||
{{- else }}
|
||||
<!-- Otherwise treat it as a site/global path -->
|
||||
<meta property="og:image" content="{{ . | absURL }}">
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else -}}
|
||||
{{- with $.Site.Params.images }}
|
||||
<meta property="og:image" content="{{ index . 0 | absURL }}">
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
{{- if .IsPage }}
|
||||
{{- $iso8601 := "2006-01-02T15:04:05-07:00" -}}
|
||||
<meta property="article:section" content="{{ .Section }}">
|
||||
{{ with .PublishDate }}
|
||||
<meta
|
||||
property="article:published_time"
|
||||
{{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
|
||||
{{ end }}
|
||||
{{ with .Lastmod }}
|
||||
<meta
|
||||
property="article:modified_time"
|
||||
{{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
|
||||
{{ end }}
|
||||
{{- end -}}
|
||||
|
||||
{{- with .Params.audio }}<meta property="og:audio" content="{{ . }}">{{ end }}
|
||||
{{- with .Params.locale }}
|
||||
<meta property="og:locale" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{- with .Site.Params.title }}
|
||||
<meta property="og:site_name" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{- with .Params.videos }}
|
||||
{{- range . }}
|
||||
<meta property="og:video" content="{{ . | absURL }}">
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{- /* If it is part of a series, link to related articles */}}
|
||||
{{- $permalink := .Permalink }}
|
||||
{{- $siteSeries := .Site.Taxonomies.series }}
|
||||
{{ with .Params.series }}
|
||||
{{- range $name := . }}
|
||||
{{- $series := index $siteSeries ($name | urlize) }}
|
||||
{{- range $page := first 6 $series.Pages }}
|
||||
{{- if ne $page.Permalink $permalink }}
|
||||
<meta property="og:see_also" content="{{ $page.Permalink }}">
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{- /* Facebook Page Admin ID for Domain Insights */}}
|
||||
{{- with site.Params.social.facebook_admin }}
|
||||
<meta property="fb:admins" content="{{ . }}">
|
||||
{{ end }}
|
||||
15
themes/hextra/layouts/_partials/scripts.html
Normal file
15
themes/hextra/layouts/_partials/scripts.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{{/* Core scripts (theme, menu, tabs, etc.) */}}
|
||||
{{- partial "scripts/core.html" . -}}
|
||||
|
||||
{{/* Search */}}
|
||||
{{- partial "scripts/search.html" . -}}
|
||||
|
||||
{{/* Mermaid */}}
|
||||
{{- if (.Store.Get "hasMermaid") -}}
|
||||
{{- partial "scripts/mermaid.html" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Asciinema */}}
|
||||
{{- if (.Store.Get "hasAsciinema") -}}
|
||||
{{- partial "scripts/asciinema.html" . -}}
|
||||
{{- end -}}
|
||||
114
themes/hextra/layouts/_partials/scripts/asciinema.html
Normal file
114
themes/hextra/layouts/_partials/scripts/asciinema.html
Normal file
@@ -0,0 +1,114 @@
|
||||
{{- /* Asciinema */ -}}
|
||||
|
||||
{{- $asciinemaBase := "" -}}
|
||||
{{- $useDefaultCdn := true -}}
|
||||
{{- with site.Params.asciinema.base -}}
|
||||
{{- $asciinemaBase = . -}}
|
||||
{{- $useDefaultCdn = false -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $asciinemaJsAsset := "" -}}
|
||||
{{- with site.Params.asciinema.js -}}
|
||||
{{- $asciinemaJsAsset = . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $asciinemaCssAsset := "" -}}
|
||||
{{- with site.Params.asciinema.css -}}
|
||||
{{- $asciinemaCssAsset = . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* If only js/css is set without base, use local asset loading */ -}}
|
||||
{{- if and $useDefaultCdn (or (ne $asciinemaJsAsset "") (ne $asciinemaCssAsset "")) -}}
|
||||
{{- $useDefaultCdn = false -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Set default CDN base if needed */ -}}
|
||||
{{- if $useDefaultCdn -}}
|
||||
{{- $asciinemaBase = "https://cdn.jsdelivr.net/npm/asciinema-player@latest/dist/bundle" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $isRemoteBase := or (strings.HasPrefix $asciinemaBase "http://") (strings.HasPrefix $asciinemaBase "https://") -}}
|
||||
{{- $minSuffix := cond hugo.IsProduction ".min" "" -}}
|
||||
|
||||
{{- /* CSS retrieval: get raw CSS from either local asset or remote, then process */ -}}
|
||||
{{- if $isRemoteBase -}}
|
||||
{{- $cssPath := cond (ne $asciinemaCssAsset "") $asciinemaCssAsset "asciinema-player.css" -}}
|
||||
{{- $asciinemaCssUrl := printf "%s/%s" $asciinemaBase $cssPath -}}
|
||||
{{- with try (resources.GetRemote $asciinemaCssUrl) -}}
|
||||
{{- with .Err -}}
|
||||
{{- errorf "Could not retrieve Asciinema css file from %s. Reason: %s." $asciinemaCssUrl . -}}
|
||||
{{- else with .Value -}}
|
||||
{{- with resources.Copy "css/asciinema-player.css" . -}}
|
||||
{{- $asciinemaCss := . | fingerprint -}}
|
||||
<link rel="stylesheet" href="{{ $asciinemaCss.RelPermalink }}" integrity="{{ $asciinemaCss.Data.Integrity }}" crossorigin="anonymous" />
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else if $asciinemaCssAsset -}}
|
||||
{{- with resources.Get $asciinemaCssAsset -}}
|
||||
{{- $asciinemaCss := . | fingerprint -}}
|
||||
<link rel="stylesheet" href="{{ $asciinemaCss.RelPermalink }}" integrity="{{ $asciinemaCss.Data.Integrity }}" crossorigin="anonymous" />
|
||||
{{- else -}}
|
||||
{{- errorf "Asciinema css asset not found at %q" $asciinemaCssAsset -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* JS retrieval: get raw JS from either local asset or remote, then process */ -}}
|
||||
{{- if $isRemoteBase -}}
|
||||
{{- $jsPath := cond (ne $asciinemaJsAsset "") $asciinemaJsAsset (printf "asciinema-player%s.js" $minSuffix) -}}
|
||||
{{- $asciinemaJsUrl := printf "%s/%s" $asciinemaBase $jsPath -}}
|
||||
{{- with try (resources.GetRemote $asciinemaJsUrl) -}}
|
||||
{{- with .Err -}}
|
||||
{{- errorf "Could not retrieve Asciinema js file from %s. Reason: %s." $asciinemaJsUrl . -}}
|
||||
{{- else with .Value -}}
|
||||
{{- with resources.Copy (printf "js/asciinema-player%s.js" $minSuffix) . -}}
|
||||
{{- $asciinemaJs := . | fingerprint -}}
|
||||
<script defer src="{{ $asciinemaJs.RelPermalink }}" integrity="{{ $asciinemaJs.Data.Integrity }}" crossorigin="anonymous"></script>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else if $asciinemaJsAsset -}}
|
||||
{{- with resources.Get $asciinemaJsAsset -}}
|
||||
{{- $asciinemaJs := . | fingerprint -}}
|
||||
<script defer src="{{ $asciinemaJs.RelPermalink }}" integrity="{{ $asciinemaJs.Data.Integrity }}" crossorigin="anonymous"></script>
|
||||
{{- else -}}
|
||||
{{- errorf "Asciinema js asset not found at %q" $asciinemaJsAsset -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Fix play button position issue
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
.ap-player .ap-overlay-start .ap-play-button span > svg {
|
||||
display: inline;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
// Initialize asciinema players
|
||||
document.querySelectorAll(".asciinema-player").forEach((el) => {
|
||||
const castFile = el.dataset.castFile;
|
||||
const theme = el.dataset.theme || "asciinema";
|
||||
const speed = parseFloat(el.dataset.speed) || 1;
|
||||
const autoplay = el.dataset.autoplay === "true";
|
||||
const loop = el.dataset.loop === "true";
|
||||
const poster = el.dataset.poster || "";
|
||||
const markers = el.dataset.markers ? JSON.parse(el.dataset.markers) : [];
|
||||
|
||||
// Create asciinema player
|
||||
if (window.AsciinemaPlayer) {
|
||||
window.AsciinemaPlayer.create(castFile, el, {
|
||||
theme: theme,
|
||||
speed: speed,
|
||||
autoplay: autoplay,
|
||||
loop: loop,
|
||||
poster: poster || undefined,
|
||||
markers: markers.length > 0 ? markers : undefined,
|
||||
controls: true, // Always show user controls (bottom control bar)
|
||||
idleTimeLimit: 2, // Limit terminal inactivity to 2 seconds (compress pauses longer than 2s)
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
10
themes/hextra/layouts/_partials/scripts/core.html
Normal file
10
themes/hextra/layouts/_partials/scripts/core.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{- $scriptsBody := slice }}
|
||||
{{- range resources.Match "js/core/*.js" -}}
|
||||
{{ $scriptsBody = $scriptsBody | append (resources.ExecuteAsTemplate .Name $ .) }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $scripts := $scriptsBody | resources.Concat "js/main.js" -}}
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- $scripts = $scripts | minify | fingerprint -}}
|
||||
{{- end -}}
|
||||
<script defer src="{{ $scripts.RelPermalink }}" integrity="{{ $scripts.Data.Integrity }}"></script>
|
||||
92
themes/hextra/layouts/_partials/scripts/katex.html
Normal file
92
themes/hextra/layouts/_partials/scripts/katex.html
Normal file
@@ -0,0 +1,92 @@
|
||||
{{- /* KaTeX CSS loader
|
||||
|
||||
Behavior (driven by site.params.math.katex):
|
||||
- base (remote URL) + optional css:
|
||||
- Construct remote CSS URL: "{{ base }}/{{ css | default "katex[.min].css" }}".
|
||||
- Fetch via resources.GetRemote, rewrite font URLs to "{{ base }}/fonts/...".
|
||||
- Build and fingerprint; emit <link rel="stylesheet" integrity>.
|
||||
- base (local path or not set) + css (asset path):
|
||||
- Read CSS from Hugo assets via resources.Get; DO NOT rewrite font URLs.
|
||||
- Build and fingerprint; emit <link rel="stylesheet" integrity>.
|
||||
- base (local path) only (no css):
|
||||
- Link directly to "{{ base }}/katex[.min].css" (no processing).
|
||||
- Nothing set:
|
||||
- Default to CDN latest base; same as remote path above.
|
||||
|
||||
Additional:
|
||||
- assets: optional list to publish extra assets. CSS/JS get tags with integrity (JS loads async).
|
||||
*/ -}}
|
||||
{{- $noop := .WordCount -}}
|
||||
|
||||
{{- $katexBase := "" -}}
|
||||
{{- with site.Params.math.katex.base -}}
|
||||
{{- $katexBase = . -}}
|
||||
{{- else -}}
|
||||
{{- if not site.Params.math.katex.css -}}
|
||||
{{- $katexBase = "https://cdn.jsdelivr.net/npm/katex@latest/dist" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $katexCssAsset := "" -}}
|
||||
{{- with site.Params.math.katex.css -}}
|
||||
{{- $katexCssAsset = . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $s := newScratch -}}
|
||||
{{- $isRemoteBase := or (strings.HasPrefix $katexBase "http://") (strings.HasPrefix $katexBase "https://") -}}
|
||||
|
||||
{{- /* CSS retrieval consolidated: get raw CSS from either local asset or remote, then process once */ -}}
|
||||
{{- $minSuffix := cond hugo.IsProduction ".min" "" -}}
|
||||
{{- if $isRemoteBase -}}
|
||||
{{- $cssPath := cond (ne $katexCssAsset "") $katexCssAsset (printf "katex%s.css" $minSuffix) -}}
|
||||
{{- $katexCssUrl := printf "%s/%s" $katexBase $cssPath -}}
|
||||
{{- with try (resources.GetRemote $katexCssUrl) -}}
|
||||
{{- with .Err -}}
|
||||
{{- errorf "Could not retrieve KaTeX css file from %s. Reason: %s." $katexCssUrl . -}}
|
||||
{{- else with .Value -}}
|
||||
{{- $s.Set "katexCssValue" .Content -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else if $katexCssAsset -}}
|
||||
{{- with resources.Get $katexCssAsset -}}
|
||||
{{- $s.Set "katexCssValue" .Content -}}
|
||||
{{- else -}}
|
||||
{{- errorf "KaTeX css asset not found at %q" $katexCssAsset -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- with $s.Get "katexCssValue" -}}
|
||||
{{- $cssContent := . -}}
|
||||
{{- if $isRemoteBase -}}
|
||||
{{- $fontPattern := "url(fonts/" -}}
|
||||
{{- $fontSub := printf "url(%s/fonts/" $katexBase -}}
|
||||
{{- $cssContent = strings.Replace $cssContent $fontPattern $fontSub -}}
|
||||
{{- end -}}
|
||||
{{- with resources.FromString (printf "css/katex%s.css" $minSuffix) $cssContent -}}
|
||||
{{- $css := . | fingerprint "sha512" -}}
|
||||
<link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}" />
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- if not $isRemoteBase -}}
|
||||
{{- $cssPath := cond (ne $katexCssAsset "") $katexCssAsset (printf "katex%s.css" $minSuffix) -}}
|
||||
<link rel="stylesheet" href="{{ printf "%s/%s" $katexBase $cssPath }}" />
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Optionally publish files (fonts, css, js, etc.) from assets and emit tags for css/js with integrity and crossorigin */ -}}
|
||||
{{- with site.Params.math.katex.assets -}}
|
||||
{{- range . -}}
|
||||
{{- with resources.Get . -}}
|
||||
{{- $name := .Name | lower -}}
|
||||
{{- if strings.HasSuffix $name ".css" -}}
|
||||
{{- $built := . | fingerprint "sha512" -}}
|
||||
<link rel="stylesheet" href="{{ $built.RelPermalink }}" integrity="{{ $built.Data.Integrity }}" crossorigin="anonymous" />
|
||||
{{- else if or (strings.HasSuffix $name ".js") (strings.HasSuffix $name ".mjs") -}}
|
||||
{{- $built := . | fingerprint "sha512" -}}
|
||||
<script src="{{ $built.RelPermalink }}" async integrity="{{ $built.Data.Integrity }}" crossorigin="anonymous"></script>
|
||||
{{- else -}}
|
||||
{{- .Publish -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
20
themes/hextra/layouts/_partials/scripts/mathjax.html
Normal file
20
themes/hextra/layouts/_partials/scripts/mathjax.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{/* MathJax */}}
|
||||
{{ $mathjaxJsUrl := "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" -}}
|
||||
<script defer id="MathJax-script" src="{{ $mathjaxJsUrl }}" crossorigin="anonymous" async></script>
|
||||
<script>
|
||||
MathJax = {
|
||||
loader: {
|
||||
load: ["ui/safe"],
|
||||
},
|
||||
tex: {
|
||||
displayMath: [
|
||||
["\\[", "\\]"],
|
||||
["$$", "$$"],
|
||||
],
|
||||
inlineMath: [
|
||||
["\\(", "\\)"],
|
||||
["$", "$"],
|
||||
],
|
||||
},
|
||||
};
|
||||
</script>
|
||||
79
themes/hextra/layouts/_partials/scripts/mermaid.html
Normal file
79
themes/hextra/layouts/_partials/scripts/mermaid.html
Normal file
@@ -0,0 +1,79 @@
|
||||
{{- /* Mermaid */ -}}
|
||||
|
||||
{{- $mermaidBase := "" -}}
|
||||
{{- $useDefaultCdn := true -}}
|
||||
{{- with site.Params.mermaid.base -}}
|
||||
{{- $mermaidBase = . -}}
|
||||
{{- $useDefaultCdn = false -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $mermaidJsAsset := "" -}}
|
||||
{{- with site.Params.mermaid.js -}}
|
||||
{{- $mermaidJsAsset = . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* If only js is set without base, use local asset loading */ -}}
|
||||
{{- if and $useDefaultCdn (ne $mermaidJsAsset "") -}}
|
||||
{{- $useDefaultCdn = false -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Set default CDN base if needed */ -}}
|
||||
{{- if $useDefaultCdn -}}
|
||||
{{- $mermaidBase = "https://cdn.jsdelivr.net/npm/mermaid@latest/dist" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $isRemoteBase := or (strings.HasPrefix $mermaidBase "http://") (strings.HasPrefix $mermaidBase "https://") -}}
|
||||
{{- $minSuffix := cond hugo.IsProduction ".min" "" -}}
|
||||
|
||||
{{- /* JS retrieval: get raw JS from either local asset or remote, then process */ -}}
|
||||
{{- if $isRemoteBase -}}
|
||||
{{- $jsPath := cond (ne $mermaidJsAsset "") $mermaidJsAsset (printf "mermaid%s.js" $minSuffix) -}}
|
||||
{{- $mermaidJsUrl := printf "%s/%s" $mermaidBase $jsPath -}}
|
||||
{{- with try (resources.GetRemote $mermaidJsUrl) -}}
|
||||
{{- with .Err -}}
|
||||
{{- errorf "Could not retrieve Mermaid js file from %s. Reason: %s." $mermaidJsUrl . -}}
|
||||
{{- else with .Value -}}
|
||||
{{- with resources.Copy (printf "js/mermaid%s.js" $minSuffix) . -}}
|
||||
{{- $mermaidJs := . | fingerprint -}}
|
||||
<script defer src="{{ $mermaidJs.RelPermalink }}" integrity="{{ $mermaidJs.Data.Integrity }}" crossorigin="anonymous"></script>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else if $mermaidJsAsset -}}
|
||||
{{- with resources.Get $mermaidJsAsset -}}
|
||||
{{- $mermaidJs := . | fingerprint -}}
|
||||
<script defer src="{{ $mermaidJs.RelPermalink }}" integrity="{{ $mermaidJs.Data.Integrity }}" crossorigin="anonymous"></script>
|
||||
{{- else -}}
|
||||
{{- errorf "Mermaid js asset not found at %q" $mermaidJsAsset -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Store original mermaid code for each diagram
|
||||
document.querySelectorAll(".mermaid").forEach((el) => {
|
||||
el.dataset.original = el.innerHTML;
|
||||
});
|
||||
|
||||
const theme = document.documentElement.classList.contains("dark") ? "dark" : "default";
|
||||
mermaid.initialize({ startOnLoad: true, theme: theme });
|
||||
|
||||
let timeout;
|
||||
new MutationObserver(() => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const theme = document.documentElement.classList.contains("dark") ? "dark" : "default";
|
||||
document.querySelectorAll(".mermaid").forEach((el) => {
|
||||
// Reset to original content, preserving HTML
|
||||
el.innerHTML = el.dataset.original;
|
||||
el.removeAttribute("data-processed");
|
||||
});
|
||||
mermaid.initialize({ startOnLoad: true, theme: theme });
|
||||
mermaid.init();
|
||||
}, 150);
|
||||
}).observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
});
|
||||
</script>
|
||||
26
themes/hextra/layouts/_partials/scripts/search.html
Normal file
26
themes/hextra/layouts/_partials/scripts/search.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{{/* Search */}}
|
||||
{{- if (site.Params.search.enable | default true) -}}
|
||||
{{- $searchType := site.Params.search.type | default "flexsearch" -}}
|
||||
{{- if eq $searchType "flexsearch" -}}
|
||||
{{- $jsSearchScript := printf "%s.search.js" .Language.Lang -}}
|
||||
{{- $jsSearch := resources.Get "js/flexsearch.js" | resources.ExecuteAsTemplate $jsSearchScript . -}}
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- $jsSearch = $jsSearch | minify | fingerprint -}}
|
||||
{{- end -}}
|
||||
{{- $flexSearchVersion := site.Params.search.flexsearch.version | default "0.8.143" -}}
|
||||
{{- $flexSearchJsUrl := printf "https://cdn.jsdelivr.net/npm/flexsearch@%s/dist/flexsearch.bundle%s.js" $flexSearchVersion (cond hugo.IsProduction ".min" ".debug") -}}
|
||||
{{ with try (resources.GetRemote $flexSearchJsUrl) -}}
|
||||
{{ with .Err -}}
|
||||
{{ errorf "Could not retrieve FlexSearch js file from %s. Reason: %s." $flexSearchJsUrl . -}}
|
||||
{{ else with.Value -}}
|
||||
{{ with resources.Copy (printf "js/flexsearch.js") . -}}
|
||||
{{ $flexSearchJs := . | fingerprint -}}
|
||||
<script defer src="{{ $flexSearchJs.RelPermalink }}" integrity="{{ $flexSearchJs.Data.Integrity }}" crossorigin="anonymous"></script>
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
<script defer src="{{ $jsSearch.RelPermalink }}" integrity="{{ $jsSearch.Data.Integrity }}"></script>
|
||||
{{- else -}}
|
||||
{{- warnf `search type "%s" is not supported` $searchType -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
26
themes/hextra/layouts/_partials/search.html
Normal file
26
themes/hextra/layouts/_partials/search.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{{- $placeholder := (T "searchPlaceholder") | default "Search..." -}}
|
||||
|
||||
|
||||
<div class="hextra-search-wrapper hx:relative hx:md:w-64">
|
||||
<div class="hx:relative hx:flex hx:items-center hx:text-gray-900 hx:contrast-more:text-gray-800 hx:dark:text-gray-300 hx:contrast-more:dark:text-gray-300">
|
||||
<input
|
||||
placeholder="{{ $placeholder }}"
|
||||
class="hextra-search-input hx:focus:hextra-focus hx:block hx:w-full hx:appearance-none hx:rounded-lg hx:px-3 hx:py-2 hx:transition-colors hx:text-base hx:leading-tight hx:md:text-sm hx:bg-black/[.05] hx:dark:bg-gray-50/10 hx:focus:bg-white hx:dark:focus:bg-dark hx:placeholder:text-gray-500 hx:dark:placeholder:text-gray-400 hx:contrast-more:border hx:contrast-more:border-current"
|
||||
type="search"
|
||||
value=""
|
||||
spellcheck="false"
|
||||
/>
|
||||
<kbd
|
||||
class="hx:absolute hx:my-1.5 hx:select-none hx:ltr:right-1.5 hx:rtl:left-1.5 hx:h-5 hx:rounded-sm hx:bg-white hx:px-1.5 hx:font-mono hx:text-[10px] hx:font-medium hx:text-gray-500 hx:border hx:border-gray-200 hx:dark:border-gray-100/20 hx:dark:bg-dark/50 hx:contrast-more:border-current hx:contrast-more:text-current hx:contrast-more:dark:border-current hx:items-center hx:gap-1 hx:transition-opacity hx:pointer-events-none hx:hidden hx:sm:flex"
|
||||
>
|
||||
CTRL K
|
||||
</kbd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul
|
||||
class="hextra-search-results hextra-scrollbar hx:hidden hx:border hx:border-gray-200 hx:bg-white hx:text-gray-100 hx:dark:border-neutral-800 hx:dark:bg-neutral-900 hx:absolute hx:top-full hx:z-20 hx:mt-2 hx:overflow-auto hx:overscroll-contain hx:rounded-xl hx:py-2.5 hx:shadow-xl hx:max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] hx:md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] hx:inset-x-0 hx:ltr:md:left-auto hx:rtl:md:right-auto hx:contrast-more:border hx:contrast-more:border-gray-900 hx:contrast-more:dark:border-gray-50 hx:w-screen hx:min-h-[100px] hx:max-w-[min(calc(100vw-2rem),calc(100%+20rem))]"
|
||||
style="transition: max-height 0.2s ease 0s;"
|
||||
></ul>
|
||||
</div>
|
||||
</div>
|
||||
38
themes/hextra/layouts/_partials/shortcodes/badge.html
Normal file
38
themes/hextra/layouts/_partials/shortcodes/badge.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{{- $content := .content -}}
|
||||
{{- $color := .color | default .type | default "" -}}{{- /* Compatibility with previous parameter. */ -}}
|
||||
{{- $class := .class | default "" -}}
|
||||
{{- $border := .border | default false -}}
|
||||
{{- $icon := .icon | default "" -}}
|
||||
|
||||
{{- /* Compatibility with previous names. */ -}}
|
||||
{{- $mapping := (dict
|
||||
"default" "gray"
|
||||
"tip" "green"
|
||||
"info" "blue"
|
||||
"warning" "yellow"
|
||||
"error" "red"
|
||||
"important" "purple"
|
||||
)
|
||||
-}}
|
||||
{{- $color = index $mapping $color | default $color | default "gray" -}}
|
||||
|
||||
{{- $styleClass := newScratch -}}
|
||||
{{- $styleClass.Set "gray" "hx:text-gray-600 hx:bg-gray-100 hx:dark:bg-neutral-800 hx:dark:text-neutral-200 hx:border-gray-200 hx:dark:border-neutral-700" -}}
|
||||
{{- $styleClass.Set "purple" "hx:border-purple-200 hx:bg-purple-100 hx:text-purple-900 hx:dark:border-purple-200/30 hx:dark:bg-purple-900/30 hx:dark:text-purple-200" -}}
|
||||
{{- $styleClass.Set "indigo" "hx:border-indigo-200 hx:bg-indigo-100 hx:text-indigo-900 hx:dark:border-indigo-200/30 hx:dark:bg-indigo-900/30 hx:dark:text-indigo-200" -}}
|
||||
{{- $styleClass.Set "blue" "hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200" -}}
|
||||
{{- $styleClass.Set "green" "hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200" -}}
|
||||
{{- $styleClass.Set "yellow" "hx:border-yellow-100 hx:bg-yellow-50 hx:text-yellow-900 hx:dark:border-yellow-200/30 hx:dark:bg-yellow-700/30 hx:dark:text-yellow-200" -}}
|
||||
{{- $styleClass.Set "orange" "hx:border-orange-100 hx:bg-orange-50 hx:text-orange-800 hx:dark:border-orange-400/30 hx:dark:bg-orange-400/20 hx:dark:text-orange-300" -}}
|
||||
{{- $styleClass.Set "amber" "hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200" -}}
|
||||
{{- $styleClass.Set "red" "hx:border-red-200 hx:bg-red-100 hx:text-red-900 hx:dark:border-red-200/30 hx:dark:bg-red-900/30 hx:dark:text-red-200" -}}
|
||||
|
||||
{{- $borderClass := cond (eq $border true) "hx:border" "" -}}
|
||||
{{- $badgeClass := or ($styleClass.Get $color) ($styleClass.Get "gray") -}}
|
||||
<div class="hextra-badge {{ $class }}">
|
||||
<div class="hx:inline-flex hx:gap-1 hx:items-center hx:rounded-full hx:px-2.5 hx:leading-6 hx:text-[.65rem] {{ $borderClass }} {{ $badgeClass }}">
|
||||
{{- with $icon -}}{{- partial "utils/icon" (dict "name" . "attributes" "height=12") -}}{{- end -}}
|
||||
{{- $content -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- /* Strip trailing newline. */ -}}
|
||||
28
themes/hextra/layouts/_partials/shortcodes/callout.html
Normal file
28
themes/hextra/layouts/_partials/shortcodes/callout.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{{- $content := .content -}}
|
||||
{{- $emoji := .emoji -}}
|
||||
{{- $icon := .icon -}}
|
||||
|
||||
{{- $defaultClass := "hx:border-orange-100 hx:bg-orange-50 hx:text-orange-800 hx:dark:border-orange-400/30 hx:dark:bg-orange-400/20 hx:dark:text-orange-300" -}}
|
||||
|
||||
{{- $class := .class | default $defaultClass -}}
|
||||
|
||||
|
||||
<div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current {{ $class }}">
|
||||
<div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2">
|
||||
{{- with $emoji -}}
|
||||
<div class="hx:select-none hx:text-xl" style="font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';">
|
||||
{{- . -}}
|
||||
</div>
|
||||
{{- else -}}
|
||||
{{- with $icon -}}
|
||||
{{ partial "utils/icon.html" (dict "name" . "attributes" `height=1.2em class="hx:inline-block hx:align-middle"`) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<div class="hx:w-full hx:min-w-0 hx:leading-7">
|
||||
<div class="hx:mt-6 hx:leading-7 hx:first:mt-0">
|
||||
{{- $content -}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
69
themes/hextra/layouts/_partials/shortcodes/card.html
Normal file
69
themes/hextra/layouts/_partials/shortcodes/card.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{{- $link := .link -}}
|
||||
{{- $title := .title -}}
|
||||
{{- $icon := .icon -}}
|
||||
{{- $subtitle := .subtitle -}}
|
||||
{{- $image := .image -}}
|
||||
{{- $alt := .alt | default $title -}}
|
||||
{{- $width := .width -}}
|
||||
{{- $height := .height -}}
|
||||
{{- $imageStyle := .imageStyle -}}
|
||||
{{- $tag := .tag -}}
|
||||
{{- $tagColor := .tagColor | default .tagType | default "" -}}{{- /* Compatibility with previous parameter. */ -}}
|
||||
{{- $tagBorder := not (eq .tagBorder false) | default true }}
|
||||
{{- $tagIcon := .tagIcon -}}
|
||||
|
||||
{{ $linkClass := "hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900" }}
|
||||
{{- with $image -}}
|
||||
{{ $linkClass = "hx:hover:border-gray-300 hx:bg-gray-100 hx:shadow-sm hx:dark:border-neutral-700 hx:dark:bg-neutral-800 hx:dark:text-gray-50 hx:hover:shadow-lg hx:dark:hover:border-neutral-500 hx:dark:hover:bg-neutral-700" }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $external := strings.HasPrefix $link "http" -}}
|
||||
{{- $href := cond (strings.HasPrefix $link "/") ($link | relURL) $link -}}
|
||||
|
||||
|
||||
<a
|
||||
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 {{ $linkClass }}"
|
||||
{{- if $link -}}
|
||||
href="{{ $href }}"
|
||||
{{ with $external }}target="_blank" rel="noreferrer"{{ end -}}
|
||||
{{- end -}}
|
||||
>
|
||||
{{- with $image -}}
|
||||
<img
|
||||
alt="{{ $alt }}"
|
||||
class="hextra-card-image"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
src="{{ $image | safeURL }}"
|
||||
{{ with $width }}width="{{ . }}"{{ end }}
|
||||
{{ with $height }}height="{{ . }}"{{ end }}
|
||||
{{ with $imageStyle }}style="{{ . | safeCSS }}"{{ end }}
|
||||
/>
|
||||
{{- end -}}
|
||||
|
||||
{{- $padding := "hx:p-4" -}}
|
||||
{{- with $subtitle -}}
|
||||
{{- $padding = "hx:pt-4 hx:px-4" -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 {{ $padding }} hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50">
|
||||
{{- with $icon }}{{ partial "utils/icon.html" (dict "name" $icon) -}}{{- end -}}
|
||||
{{- $title -}}
|
||||
</span>
|
||||
{{- with $subtitle -}}
|
||||
<div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2">{{- $subtitle | markdownify -}}</div>
|
||||
{{- end -}}
|
||||
|
||||
{{- if $tag }}
|
||||
{{- partial "shortcodes/badge.html" (dict
|
||||
"content" $tag
|
||||
"color" $tagColor
|
||||
"class" "hextra-card-tag"
|
||||
"border" $tagBorder
|
||||
"icon" $tagIcon
|
||||
)
|
||||
-}}
|
||||
{{- end -}}
|
||||
</a>
|
||||
{{- /* Strip trailing newline. */ -}}
|
||||
6
themes/hextra/layouts/_partials/shortcodes/cards.html
Normal file
6
themes/hextra/layouts/_partials/shortcodes/cards.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{{- $cols := .cols | default 3 -}}
|
||||
{{- $content := .content -}}
|
||||
|
||||
<div class="hextra-cards hx:mt-4 hx:gap-4 hx:grid not-prose" style="--hextra-cards-grid-cols: {{ $cols }};">
|
||||
{{- $content -}}
|
||||
</div>
|
||||
69
themes/hextra/layouts/_partials/shortcodes/tabs.html
Normal file
69
themes/hextra/layouts/_partials/shortcodes/tabs.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{{- $tabsID := .id }}
|
||||
|
||||
{{- /*
|
||||
The `tabs` parameter is a list of dict with the following keys:
|
||||
- `id`: (int) the ID of the tab (the Ordinal of the tab shortcode).
|
||||
- `name`: (string) the name of the tab (the title).
|
||||
- `content`: (string) the content of the tab.
|
||||
- `selected`: (bool) whether the tab is selected.
|
||||
*/ -}}
|
||||
{{- $tabs := .tabs }}
|
||||
|
||||
{{- if eq (len $tabs) 0 -}}
|
||||
{{ errorf "tabs must have at least one tab" }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $enableSync := .enableSync }}
|
||||
|
||||
{{- /* Create group data for syncing and select the first tab if none is selected. */ -}}
|
||||
{{- $selectedIndex := 0 -}}
|
||||
{{ $dataTabGroup := slice -}}
|
||||
|
||||
{{- range $i, $item := $tabs -}}
|
||||
{{- $dataTabGroup = $dataTabGroup | append ($item.name) -}}
|
||||
|
||||
{{- if $item.selected -}}
|
||||
{{- $selectedIndex = $i -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Generate a unique ID for each tab group. */ -}}
|
||||
{{- $globalID := printf "tabs-%02v" $tabsID -}}
|
||||
|
||||
<div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain">
|
||||
<div
|
||||
class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800"
|
||||
{{ if $enableSync }} data-tab-group="{{ delimit $dataTabGroup `,` }}"{{ end }}
|
||||
>
|
||||
{{- range $i, $item := $tabs -}}
|
||||
<button
|
||||
class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white"
|
||||
role="tab"
|
||||
type="button"
|
||||
aria-controls="tabs-panel-{{ $globalID }}-{{ $item.id }}"
|
||||
{{- if eq $i $selectedIndex -}}
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
data-state="selected"
|
||||
{{- end }}
|
||||
>
|
||||
{{- $item.name -}}
|
||||
</button>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{- range $i, $item := $tabs -}}
|
||||
<div
|
||||
class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block"
|
||||
id="tabs-panel-{{ $globalID }}-{{ $item.id }}"
|
||||
role="tabpanel"
|
||||
{{- if eq $i $selectedIndex -}}
|
||||
tabindex="0"
|
||||
data-state="selected"
|
||||
{{ end -}}
|
||||
>
|
||||
{{- $item.content | markdownify -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
188
themes/hextra/layouts/_partials/sidebar.html
Normal file
188
themes/hextra/layouts/_partials/sidebar.html
Normal file
@@ -0,0 +1,188 @@
|
||||
{{- $context := .context -}}
|
||||
|
||||
{{- $disableSidebar := .disableSidebar | default false -}}
|
||||
{{- $displayPlaceholder := .displayPlaceholder | default false -}}
|
||||
|
||||
{{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}}
|
||||
{{- $pageURL := $context.RelPermalink -}}
|
||||
|
||||
{{- if .context.Params.sidebar.hide -}}
|
||||
{{- $disableSidebar = true -}}
|
||||
{{- $displayPlaceholder = false -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $sidebarClass := "hx:md:sticky" -}}
|
||||
{{- if $disableSidebar -}}
|
||||
{{- if $displayPlaceholder -}}
|
||||
{{- $sidebarClass = "hx:md:hidden hx:xl:block" -}}
|
||||
{{- else -}}
|
||||
{{- $sidebarClass = "hx:md:hidden" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<aside class="hextra-sidebar-container hx:flex hx:flex-col hx:print:hidden hx:md:top-16 hx:md:shrink-0 hx:md:w-64 hx:md:self-start hx:max-md:[transform:translate3d(0,-100%,0)] {{ $sidebarClass }}">
|
||||
<!-- Search bar on small screen -->
|
||||
<div class="hx:px-4 hx:pt-4 hx:md:hidden">
|
||||
{{ partial "search.html" }}
|
||||
</div>
|
||||
<div class="hextra-scrollbar hx:overflow-y-auto hx:overflow-x-hidden hx:p-4 hx:grow hx:md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]">
|
||||
<ul class="hx:flex hx:flex-col hx:gap-1 hx:md:hidden">
|
||||
<!-- Nav -->
|
||||
{{ template "sidebar-main" (dict "context" site.Home "pageURL" $pageURL "page" $context "toc" true) -}}
|
||||
{{ template "sidebar-footer" }}
|
||||
</ul>
|
||||
|
||||
<!-- Sidebar on large screen -->
|
||||
{{- if $disableSidebar -}}
|
||||
{{- if $displayPlaceholder }}<div class="hx:max-xl:hidden hx:h-0 hx:w-64 hx:shrink-0"></div>{{ end -}}
|
||||
{{ .context.Store.Set "enableFooterSwitches" true }}
|
||||
{{- else -}}
|
||||
<ul class="hx:flex hx:flex-col hx:gap-1 hx:max-md:hidden">
|
||||
{{ template "sidebar-main" (dict "context" $navRoot "page" $context "pageURL" $pageURL) }}
|
||||
{{ template "sidebar-footer" }}
|
||||
</ul>
|
||||
{{ end -}}
|
||||
</div>
|
||||
{{/* Hide theme switch when sidebar is disabled */}}
|
||||
{{ $switchesClass := cond $disableSidebar "hx:md:hidden" "" -}}
|
||||
{{ $displayThemeToggle := (site.Params.theme.displayToggle | default true) -}}
|
||||
|
||||
{{ if or hugo.IsMultilingual $displayThemeToggle }}
|
||||
<div class="{{ $switchesClass }} {{ with hugo.IsMultilingual }}hx:justify-end{{ end }} hx:sticky hx:bottom-0 hx:max-h-(--menu-height) hx:bg-white hx:dark:bg-dark hx:mx-4 hx:py-4 hx:shadow-[0_-12px_16px_#fff] hx:flex hx:items-center hx:gap-2 hx:border-gray-200 hx:dark:border-neutral-800 hx:dark:shadow-[0_-12px_16px_#111] hx:contrast-more:border-neutral-400 hx:contrast-more:shadow-none hx:contrast-more:dark:shadow-none hx:border-t" data-toggle-animation="show">
|
||||
{{- with hugo.IsMultilingual -}}
|
||||
{{- partial "language-switch" (dict "context" $context "grow" true) -}}
|
||||
{{- with $displayThemeToggle }}{{ partial "theme-toggle" (dict "hideLabel" true "location" "bottom-right") }}{{ end -}}
|
||||
{{- else -}}
|
||||
{{- with $displayThemeToggle -}}
|
||||
<div class="hx:flex hx:grow hx:flex-col">{{ partial "theme-toggle" }}</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
</aside>
|
||||
|
||||
{{- define "sidebar-main" -}}
|
||||
{{ template "sidebar-tree" (dict "context" .context "level" 0 "page" .page "pageURL" .pageURL "toc" (.toc | default false)) }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "sidebar-tree" -}}
|
||||
{{- if ge .level 4 -}}
|
||||
{{- return -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $context := .context -}}
|
||||
{{- $page := .page }}
|
||||
{{- $pageURL := .page.RelPermalink -}}
|
||||
{{- $level := .level -}}
|
||||
{{- $toc := .toc | default false -}}
|
||||
|
||||
{{- with $items := union .context.RegularPages .context.Sections -}}
|
||||
{{- $items = where $items "Params.sidebar.exclude" "!=" true -}}
|
||||
{{- if eq $level 0 -}}
|
||||
{{- range $items.ByWeight }}
|
||||
{{- if .Params.sidebar.separator -}}
|
||||
<li class="[word-break:break-word] hx:mt-5 hx:mb-2 hx:px-2 hx:py-1.5 hx:text-sm hx:font-semibold hx:text-gray-900 hx:first:mt-0 hx:dark:text-gray-100">
|
||||
<span class="hx:cursor-default">{{ partial "utils/title" . }}</span>
|
||||
</li>
|
||||
{{- else -}}
|
||||
{{- $active := eq $pageURL .RelPermalink -}}
|
||||
{{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }}
|
||||
<li class="{{ if $shouldOpen }}open{{ end }}">
|
||||
{{- $linkTitle := partial "utils/title" . -}}
|
||||
{{- template "sidebar-item-link" dict "context" . "active" $active "title" $linkTitle "link" .RelPermalink -}}
|
||||
{{- if and $toc $active -}}
|
||||
{{- template "sidebar-toc" dict "page" . -}}
|
||||
{{- end -}}
|
||||
{{- template "sidebar-tree" dict "context" . "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc -}}
|
||||
</li>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<div class="hx:ltr:pr-0 hx:overflow-hidden">
|
||||
<ul class='hx:relative hx:flex hx:flex-col hx:gap-1 hx:before:absolute hx:before:inset-y-1 hx:before:w-px hx:before:bg-gray-200 hx:before:content-[""] hx:ltr:ml-3 hx:ltr:pl-3 hx:ltr:before:left-0 hx:rtl:mr-3 hx:rtl:pr-3 hx:rtl:before:right-0 hx:dark:before:bg-neutral-800'>
|
||||
{{- range $items.ByWeight }}
|
||||
{{- $active := eq $pageURL .RelPermalink -}}
|
||||
{{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }}
|
||||
{{- $linkTitle := partial "utils/title" . -}}
|
||||
<li class="hx:flex hx:flex-col {{ if $shouldOpen }}open{{ end }}">
|
||||
{{- template "sidebar-item-link" dict "context" . "active" $active "title" $linkTitle "link" .RelPermalink -}}
|
||||
{{- if and $toc $active -}}
|
||||
{{ template "sidebar-toc" dict "page" . }}
|
||||
{{- end }}
|
||||
{{ template "sidebar-tree" dict "context" . "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc }}
|
||||
</li>
|
||||
{{- end -}}
|
||||
</ul>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "sidebar-toc" -}}
|
||||
{{ $page := .page }}
|
||||
{{ with $page.Fragments.Headings }}
|
||||
<ul class='hx:flex hx:flex-col hx:gap-1 hx:relative hx:before:absolute hx:before:inset-y-1 hx:before:w-px hx:before:bg-gray-200 hx:before:content-[""] hx:dark:before:bg-neutral-800 hx:ltr:pl-3 hx:ltr:before:left-0 hx:rtl:pr-3 hx:rtl:before:right-0 hx:ltr:ml-3 hx:rtl:mr-3'>
|
||||
{{- range . }}
|
||||
{{- with .Headings }}
|
||||
{{- range . -}}
|
||||
<li>
|
||||
<a
|
||||
href="#{{ anchorize .ID }}"
|
||||
class="hx:flex hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:transition-colors [word-break:break-word] hx:cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] hx:contrast-more:border hx:gap-2 hx:before:opacity-25 hx:before:content-['#'] hx:text-gray-500 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:text-neutral-400 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50 hx:contrast-more:text-gray-900 hx:contrast-more:dark:text-gray-50 hx:contrast-more:border-transparent hx:contrast-more:hover:border-gray-900 hx:contrast-more:dark:hover:border-gray-50"
|
||||
>
|
||||
{{- .Title | safeHTML | plainify | htmlUnescape -}}
|
||||
</a>
|
||||
</li>
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
</ul>
|
||||
{{ end }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "sidebar-footer" -}}
|
||||
{{- range site.Menus.sidebar -}}
|
||||
{{- $name := or (T .Identifier) .Name -}}
|
||||
{{ if eq .Params.type "separator" }}
|
||||
<li class="[word-break:break-word] hx:mt-5 hx:mb-2 hx:px-2 hx:py-1.5 hx:text-sm hx:font-semibold hx:text-gray-900 hx:first:mt-0 hx:dark:text-gray-100">
|
||||
<span class="hx:cursor-default">{{ $name }}</span>
|
||||
</li>
|
||||
{{ else }}
|
||||
{{- $link := .URL -}}
|
||||
{{- with .PageRef -}}
|
||||
{{- if hasPrefix . "/" -}}
|
||||
{{- $link = relLangURL (strings.TrimPrefix "/" .) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<li>{{ template "sidebar-item-link" dict "active" false "title" $name "link" $link }}</li>
|
||||
{{ end }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "sidebar-item-link" -}}
|
||||
{{- $external := strings.HasPrefix .link "http" -}}
|
||||
{{- $open := .open | default true -}}
|
||||
<a
|
||||
class="hx:flex hx:items-center hx:justify-between hx:gap-2 hx:cursor-pointer hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
|
||||
{{- if .active }}
|
||||
hextra-sidebar-active-item hx:bg-primary-100 hx:font-semibold hx:text-primary-800 hx:contrast-more:border hx:contrast-more:border-primary-500 hx:dark:bg-primary-400/10 hx:dark:text-primary-600 hx:contrast-more:dark:border-primary-500
|
||||
{{- else }}
|
||||
hx:text-gray-500 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:contrast-more:border hx:contrast-more:border-transparent hx:contrast-more:text-gray-900 hx:contrast-more:hover:border-gray-900 hx:dark:text-neutral-400 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50 hx:contrast-more:dark:text-gray-50 hx:contrast-more:dark:hover:border-gray-50
|
||||
{{- end -}}"
|
||||
href="{{ .link }}"
|
||||
{{ if $external }}target="_blank" rel="noreferrer"{{ end }}
|
||||
>
|
||||
{{- .title -}}
|
||||
{{- with .context }}
|
||||
{{- if or .RegularPages .Sections }}
|
||||
<span class="hextra-sidebar-collapsible-button">
|
||||
{{- template "sidebar-collapsible-button" -}}
|
||||
</span>
|
||||
{{- end }}
|
||||
{{ end -}}
|
||||
</a>
|
||||
{{- end -}}
|
||||
|
||||
{{- define "sidebar-collapsible-button" -}}
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" class="hx:h-[18px] hx:min-w-[18px] hx:rounded-xs hx:p-0.5 hx:hover:bg-gray-800/5 hx:dark:hover:bg-gray-100/5"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" class="hx:origin-center hx:transition-transform hx:rtl:-rotate-180"></path></svg>
|
||||
{{- end -}}
|
||||
7
themes/hextra/layouts/_partials/tags.html
Normal file
7
themes/hextra/layouts/_partials/tags.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{{- $context := .context -}}
|
||||
|
||||
{{- range $tag := $context.Params.tags -}}
|
||||
{{- with $context.Site.GetPage (printf "/tags/%s" $tag) -}}
|
||||
<a class="hx:inline-block hx:whitespace-nowrap hx:mr-2 hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50" href="{{ .RelPermalink }}">#{{ $tag }}</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
68
themes/hextra/layouts/_partials/theme-toggle.html
Normal file
68
themes/hextra/layouts/_partials/theme-toggle.html
Normal file
@@ -0,0 +1,68 @@
|
||||
{{- $hideLabel := .hideLabel -}}
|
||||
{{- $iconHeight := .iconHeight | default 12 -}}
|
||||
{{- $class := .class | default "hx:h-7 hx:px-2 hx:text-xs hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50 hx:font-medium hx:text-gray-600 hx:transition-colors hx:dark:text-gray-400" -}}
|
||||
{{- $location := .location | default "bottom" -}}
|
||||
|
||||
{{- $changeTheme := (T "changeTheme") | default "Change theme" -}}
|
||||
{{- $light := (T "light") | default "Light" -}}
|
||||
{{- $dark := (T "dark") | default "Dark" -}}
|
||||
{{- $system := (T "system") | default "System" -}}
|
||||
|
||||
<div class="hx:flex hx:justify-items-start hx:group" data-theme="light">
|
||||
<button
|
||||
title="{{ $changeTheme }}"
|
||||
data-state="closed"
|
||||
data-location="{{ $location }}"
|
||||
class="hextra-theme-toggle hx:cursor-pointer hx:rounded-md hx:text-left hx:font-medium {{ $class }} hx:grow"
|
||||
type="button"
|
||||
aria-label="{{ $changeTheme }}"
|
||||
>
|
||||
<div class="hx:flex hx:items-center hx:gap-2 hx:capitalize">
|
||||
{{- partial "utils/icon.html" (dict "name" "sun" "attributes" (printf `height=%d class="hx:group-data-[theme=dark]:hidden hx:group-data-[theme=system]:hidden"` $iconHeight)) -}}
|
||||
{{- if not $hideLabel }}<span class="hx:group-data-[theme=dark]:hidden hx:group-data-[theme=system]:hidden">{{ $light }}</span>{{ end -}}
|
||||
{{- partial "utils/icon.html" (dict "name" "moon" "attributes" (printf `height=%d class="hx:group-data-[theme=light]:hidden hx:group-data-[theme=system]:hidden"` $iconHeight)) -}}
|
||||
{{- if not $hideLabel }}<span class="hx:group-data-[theme=light]:hidden hx:group-data-[theme=system]:hidden">{{ $dark }}</span>{{ end -}}
|
||||
{{- partial "utils/icon.html" (dict "name" "contrast" "attributes" (printf `height=%d class="hx:group-data-[theme=dark]:hidden hx:group-data-[theme=light]:hidden"` $iconHeight)) -}}
|
||||
{{- if not $hideLabel }}<span class="hx:group-data-[theme=dark]:hidden hx:group-data-[theme=light]:hidden">{{ $system }}</span>{{ end -}}
|
||||
</div>
|
||||
</button>
|
||||
<ul
|
||||
class="hextra-theme-toggle-options hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-md hx:ring-1 hx:ring-black/5 hx:bg-white hx:py-1 hx:text-sm hx:shadow-lg hx:dark:ring-white/20 hx:dark:bg-neutral-800"
|
||||
style="position: fixed; inset: auto auto 0px 0px; margin: 0px; min-width: 100px;"
|
||||
data-theme="light"
|
||||
>
|
||||
<li class="hx:flex hx:flex-col">
|
||||
<p
|
||||
data-item="light"
|
||||
class="hx:text-gray-800 hx:dark:text-gray-100 hx:hover:bg-primary-50 hx:hover:text-primary-600 hx:hover:dark:bg-primary-500/10 hx:hover:dark:text-primary-600 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9"
|
||||
>
|
||||
{{ $light }}
|
||||
<span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=dark]:hidden hx:group-data-[theme=system]:hidden">
|
||||
{{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}}
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
<li class="hx:flex hx:flex-col">
|
||||
<p
|
||||
data-item="dark"
|
||||
class="hx:text-gray-800 hx:dark:text-gray-100 hx:hover:bg-primary-50 hx:hover:text-primary-600 hx:hover:dark:bg-primary-500/10 hx:hover:dark:text-primary-600 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9"
|
||||
>
|
||||
{{ $dark }}
|
||||
<span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=light]:hidden hx:group-data-[theme=system]:hidden">
|
||||
{{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}}
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
<li class="hx:flex hx:flex-col">
|
||||
<p
|
||||
data-item="system"
|
||||
class="hx:text-gray-800 hx:dark:text-gray-100 hx:hover:bg-primary-50 hx:hover:text-primary-600 hx:hover:dark:bg-primary-500/10 hx:hover:dark:text-primary-600 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9"
|
||||
>
|
||||
{{ $system }}
|
||||
<span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=dark]:hidden hx:group-data-[theme=light]:hidden">
|
||||
{{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}}
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
91
themes/hextra/layouts/_partials/toc.html
Normal file
91
themes/hextra/layouts/_partials/toc.html
Normal file
@@ -0,0 +1,91 @@
|
||||
{{/* Table of Contents */}}
|
||||
{{/* TODO: toc bottom part should be able to hide */}}
|
||||
{{- $toc := .Params.toc | default true -}}
|
||||
{{- $onThisPage := (T "onThisPage") | default "On this page"}}
|
||||
{{- $tags := (T "tags") | default "Tags"}}
|
||||
{{- $editThisPage := (T "editThisPage") | default "Edit this page"}}
|
||||
{{- $backToTop := (T "backToTop") | default "Scroll to top" -}}
|
||||
|
||||
<nav class="hextra-toc hx:order-last hx:hidden hx:w-64 hx:shrink-0 hx:xl:block hx:print:hidden hx:px-4" aria-label="table of contents">
|
||||
{{- if $toc }}
|
||||
<div class="hextra-scrollbar hx:sticky hx:top-16 hx:overflow-y-auto hx:pr-4 hx:pt-6 hx:text-sm [hyphens:auto] hx:max-h-[calc(100vh-var(--navbar-height)-env(safe-area-inset-bottom))] hx:ltr:-mr-4 hx:rtl:-ml-4">
|
||||
{{- with .Fragments.Headings -}}
|
||||
<p class="hx:mb-4 hx:font-semibold hx:tracking-tight">{{ $onThisPage }}</p>
|
||||
{{- range . -}}
|
||||
<ul>
|
||||
{{- with .Headings -}}{{ template "toc-subheading" (dict "headings" . "level" 0) }}{{- end -}}
|
||||
</ul>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $borderClass := "hx:mt-8 hx:border-t hx:bg-white hx:pt-8 hx:shadow-[0_-12px_16px_white] hx:dark:bg-dark hx:dark:shadow-[0_-12px_16px_#111]" -}}
|
||||
|
||||
{{- if not .Fragments.Headings -}}
|
||||
{{- $borderClass = "" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* TOC bottom part */}}
|
||||
<div class="{{ $borderClass }} hx:sticky hx:bottom-0 hx:flex hx:flex-col hx:items-start hx:gap-2 hx:pb-8 hx:border-gray-200 hx:dark:border-neutral-800 hx:contrast-more:border-t hx:contrast-more:border-neutral-400 hx:contrast-more:shadow-none hx:contrast-more:dark:border-neutral-400">
|
||||
{{- if and site.Params.toc.displayTags .Params.tags -}}
|
||||
<div class="hx:flex hx:items-start hx:gap-x-2 hx:font-medium hx:text-xs">
|
||||
<div class="hx:text-gray-500 hx:dark:text-gray-400 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50">{{ $tags }}</div>
|
||||
<div class="hx:flex hx:flex-wrap hx:gap-y-1">
|
||||
{{ partial "tags.html" (dict "context" .) }}
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- if site.Params.editURL.enable -}}
|
||||
{{- $editURL := site.Params.editURL.base | default "" -}}
|
||||
{{- with .Params.editURL -}}
|
||||
{{/* if `editURL` is set in the front matter */}}
|
||||
{{- $editURL = . -}}
|
||||
{{- else -}}
|
||||
{{- with .File -}}
|
||||
{{/* `.FileInfo.Meta.SourceRoot` is a Hugo internal field, e.g. `/path/to/repo/content/en/` */}}
|
||||
{{- $sourceDir := replace (strings.TrimPrefix .FileInfo.Meta.BaseDir .FileInfo.Meta.SourceRoot) "\\" "/" -}}
|
||||
{{- $sourceDir = strings.TrimPrefix "/content" $sourceDir -}}
|
||||
{{- $path := replace .Path "\\" "/" -}}
|
||||
{{- $editURL = urls.JoinPath $editURL $sourceDir $path -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<a class="hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50" href="{{ $editURL }}" target="_blank" rel="noreferrer">{{ $editThisPage }}</a>
|
||||
{{- end -}}
|
||||
{{/* Scroll To Top */}}
|
||||
<button aria-hidden="true" id="backToTop" onClick="scrollUp();" class="hx:cursor-pointer hx:transition-all hx:duration-75 hx:opacity-0 hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50">
|
||||
<span>
|
||||
{{- $backToTop -}}
|
||||
</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="hx:inline hx:ltr:ml-1 hx:rtl:mr-1 hx:h-3.5 hx:w-3.5 hx:rounded-full hx:border hx:border-gray-500 hx:hover:border-gray-900 hx:dark:border-gray-400 hx:dark:hover:border-gray-100 hx:contrast-more:border-gray-800 hx:contrast-more:dark:border-gray-50">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ end -}}
|
||||
</nav>
|
||||
|
||||
{{/* TOC subheadings component. This is a recursive component that renders a list of headings. */}}
|
||||
{{- define "toc-subheading" -}}
|
||||
{{- $headings := .headings -}}
|
||||
{{- $level := .level -}}
|
||||
{{- if ge $level 6 -}}
|
||||
{{ return }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $padding := (mul $level 4) -}}
|
||||
{{- $class := cond (eq $level 0) "hx:font-medium" (printf "hx:ltr:pl-%d hx:rtl:pr-%d" $padding $padding) -}}
|
||||
|
||||
{{- range $headings }}
|
||||
{{- if .Title }}
|
||||
<li class="hx:my-2 hx:scroll-my-6 hx:scroll-py-6">
|
||||
<a class="{{ $class }} hx:inline-block hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-300 hx:contrast-more:text-gray-900 hx:contrast-more:underline hx:contrast-more:dark:text-gray-50 hx:w-full hx:break-words" href="#{{ anchorize .ID }}">
|
||||
{{- .Title | safeHTML | plainify | htmlUnescape }}
|
||||
</a>
|
||||
</li>
|
||||
{{- end -}}
|
||||
{{- with .Headings -}}
|
||||
{{ template "toc-subheading" (dict "headings" . "level" (add $level 1)) }}
|
||||
{{- end -}}
|
||||
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
31
themes/hextra/layouts/_partials/utils/extract-headings.html
Normal file
31
themes/hextra/layouts/_partials/utils/extract-headings.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{{- /*
|
||||
Extracts all headings from a page and adds them to the scratchpad.
|
||||
|
||||
The keys can be obtained from the scratchpad by using the "keys" key.
|
||||
The titles can be obtained from the scratchpad by using the "titles" key.
|
||||
|
||||
The scratchpad must be initialized with empty slices before calling this function for the keys "keys" and "titles"
|
||||
|
||||
@param {any} target The element to extract headings from.
|
||||
@param {any} scratch The scratchpad to add the keys and titles to.
|
||||
|
||||
@example {{ partial "utils/extract-headings.html" (dict "target" $h1 "scratch" $s) }}
|
||||
*/ -}}
|
||||
|
||||
{{- range $heading := index .target.Headings -}}
|
||||
{{- if and (eq $heading.Level 0) (not $heading.Title) -}}
|
||||
{{- $.scratch.Add "keys" (slice $heading.Title) -}}
|
||||
{{- else -}}
|
||||
{{- $key := (printf "%s#%s" $heading.ID $heading.Title) -}}
|
||||
{{- $.scratch.Add "keys" (slice $key) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $title := (printf "<h%d>%s" $heading.Level $heading.Title) | htmlUnescape -}}
|
||||
{{- $.scratch.Add "titles" (slice $title) -}}
|
||||
|
||||
{{- partial "utils/extract-headings.html" (dict
|
||||
"target" $heading
|
||||
"scratch" $.scratch
|
||||
)
|
||||
}}
|
||||
{{- end -}}
|
||||
21
themes/hextra/layouts/_partials/utils/file-path.html
Normal file
21
themes/hextra/layouts/_partials/utils/file-path.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{{/* This utility is used to get the file path from absolute, relative path or URL. */}}
|
||||
|
||||
{{- $path := .path -}}
|
||||
{{- $page := .page -}}
|
||||
|
||||
{{- $isLocal := not (urls.Parse $path).Scheme -}}
|
||||
{{- $isPage := and (eq $page.Kind "page") (not $page.BundleType) -}}
|
||||
{{- $startsWithSlash := hasPrefix $path "/" -}}
|
||||
{{- $startsWithRelative := hasPrefix $path "../" -}}
|
||||
|
||||
{{- if and $path $isLocal -}}
|
||||
{{- if $startsWithSlash -}}
|
||||
{{/* File under static directory */}}
|
||||
{{- $path = (relURL (strings.TrimPrefix "/" $path)) -}}
|
||||
{{- else if and $isPage (not $startsWithRelative) -}}
|
||||
{{/* File is a sibling to the individual page file */}}
|
||||
{{ $path = (printf "../%s" $path) }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- return $path -}}
|
||||
3
themes/hextra/layouts/_partials/utils/format-date.html
Normal file
3
themes/hextra/layouts/_partials/utils/format-date.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{{- with . -}}
|
||||
{{- . | time.Format (site.Params.dateFormat | default ":date_long") -}}
|
||||
{{- end -}}
|
||||
93
themes/hextra/layouts/_partials/utils/fragments.html
Normal file
93
themes/hextra/layouts/_partials/utils/fragments.html
Normal file
@@ -0,0 +1,93 @@
|
||||
{{- /*
|
||||
fragments.html - Split page content into searchable fragments
|
||||
|
||||
This partial processes a Hugo page and splits its content into fragments based on headings,
|
||||
creating a data structure suitable for search indexing. It supports different fragment types
|
||||
and handles hierarchical heading structures (h1, h2).
|
||||
|
||||
Parameters:
|
||||
- .context (Page): The Hugo page to process
|
||||
- .type (string): Fragment type - "content" (default), "heading", "title", or "summary"
|
||||
|
||||
Returns:
|
||||
- dict: Map of heading keys to content fragments
|
||||
|
||||
Example:
|
||||
Input page with content:
|
||||
# Introduction
|
||||
This is the intro text.
|
||||
## Setup
|
||||
Setup instructions here.
|
||||
# Configuration
|
||||
Config details here.
|
||||
|
||||
Output (type "content"):
|
||||
{
|
||||
"": "This is the intro text.",
|
||||
"intro#Introduction": "This is the intro text. Setup instructions here.",
|
||||
"setup#Setup": "Setup instructions here.",
|
||||
"config#Configuration": "Config details here."
|
||||
}
|
||||
|
||||
Fragment types:
|
||||
- "content": Splits page content by headings (default)
|
||||
- "heading": Returns heading keys with empty content
|
||||
- "title": Returns empty content (title handled elsewhere)
|
||||
- "summary": Returns page summary only
|
||||
*/ -}}
|
||||
|
||||
{{- /* Extract page context and fragment type */ -}}
|
||||
{{- $page := .context -}}
|
||||
{{- $type := .type | default "content" -}}
|
||||
|
||||
{{- /* Process all headings */ -}}
|
||||
{{- $s := newScratch -}}
|
||||
{{- $s.Set "keys" slice -}}
|
||||
{{- $s.Set "titles" slice -}}
|
||||
|
||||
{{- partial "utils/extract-headings.html" (dict "target" $page.Fragments "scratch" $s) -}}
|
||||
|
||||
{{- $headingKeys := $s.Get "keys" -}}
|
||||
{{- $headingTitles := $s.Get "titles" -}}
|
||||
|
||||
{{- $content := $page.Content | htmlUnescape -}}
|
||||
{{- $len := len $headingKeys -}}
|
||||
{{- $data := dict -}}
|
||||
|
||||
{{ if eq $type "content" }}
|
||||
{{/* Include full content of the page */}}
|
||||
{{ if eq $len 0 }}
|
||||
{{ $data = $data | merge (dict "" ($page.Plain | htmlUnescape | strings.TrimSpace)) }}
|
||||
{{ else }}
|
||||
{{/* Split the raw content from bottom to top */}}
|
||||
{{ range seq $len }}
|
||||
{{ $i := sub $len . }}
|
||||
{{ $headingKey := index $headingKeys $i }}
|
||||
{{ $headingTitle := index $headingTitles $i }}
|
||||
|
||||
{{ if eq $i 0 }}
|
||||
{{ $data = $data | merge (dict $headingKey ($content | plainify | htmlUnescape | strings.TrimSpace)) }}
|
||||
{{ else }}
|
||||
{{ $parts := split $content (printf "%s" $headingTitle) }}
|
||||
{{ $lastPart := index $parts (sub (len $parts) 1) }}
|
||||
|
||||
{{ $data = $data | merge (dict $headingKey ($lastPart | plainify | htmlUnescape | strings.TrimSpace)) }}
|
||||
{{ $content = strings.TrimSuffix $lastPart $content }}
|
||||
{{ $content = strings.TrimSuffix (printf "%s" $headingTitle) $content }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ else if (eq $type "heading" ) }}
|
||||
{{/* Put heading keys with empty content to the data object */}}
|
||||
{{ $data = dict "" "" }}
|
||||
{{ range $headingKeys }}
|
||||
{{ $data = $data | merge (dict . "") }}
|
||||
{{ end }}
|
||||
{{ else if (eq $type "title") }}
|
||||
{{/* Use empty data object since title is included in search-data.json */}}
|
||||
{{ $data = $data | merge (dict "" "") }}
|
||||
{{ else if (eq $type "summary" ) }}
|
||||
{{ $data = $data | merge (dict "" ($page.Summary | plainify | htmlUnescape | strings.TrimSpace)) }}
|
||||
{{ end }}
|
||||
|
||||
{{ return $data }}
|
||||
14
themes/hextra/layouts/_partials/utils/icon.html
Normal file
14
themes/hextra/layouts/_partials/utils/icon.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{{/* Render raw svg icon from .Site.Data */}}
|
||||
{{- $icon := index site.Data.icons .name -}}
|
||||
|
||||
{{- if not $icon -}}
|
||||
{{ errorf "icon %q not found" .name }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $icon = $icon | safeHTML -}}
|
||||
|
||||
{{- if .attributes -}}
|
||||
{{- $icon = replaceRE "<svg" (printf "<svg %s" .attributes) $icon -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- return ($icon | safeHTML) -}}
|
||||
25
themes/hextra/layouts/_partials/utils/lang-link.html
Normal file
25
themes/hextra/layouts/_partials/utils/lang-link.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{{/* Get relative link of a page for given language */}}
|
||||
{{/* If not found, return the homepage of the language page */}}
|
||||
|
||||
{{ $page := .context }}
|
||||
{{ $lang := .lang }}
|
||||
|
||||
{{ $link := false }}
|
||||
|
||||
{{ range $page.AllTranslations }}
|
||||
{{ if eq .Language.Lang $lang }}
|
||||
{{ $link = .RelPermalink }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if not $link }}
|
||||
{{ range where $page.Sites ".Language.Lang" $lang }}
|
||||
{{ $link = .Home.RelPermalink }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if not $link }}
|
||||
{{ $link = site.Home.RelPermalink }}
|
||||
{{ end }}
|
||||
|
||||
{{ return $link }}
|
||||
11
themes/hextra/layouts/_partials/utils/page-description.html
Normal file
11
themes/hextra/layouts/_partials/utils/page-description.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{{ with .Description | plainify | htmlUnescape -}}
|
||||
{{ . -}}
|
||||
{{ else -}}
|
||||
{{ if .IsHome -}}
|
||||
{{ with .Site.Params.description | plainify | htmlUnescape -}}
|
||||
{{ . -}}
|
||||
{{ end -}}
|
||||
{{ else -}}
|
||||
{{ .Summary | plainify | htmlUnescape | chomp -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{{- with .Params.width -}}
|
||||
<style>
|
||||
:root {
|
||||
--hextra-max-page-width: {{ cond (eq . "wide") "90rem" (cond (eq . "full") "100%" "80rem") }};
|
||||
}
|
||||
</style>
|
||||
{{- end -}}
|
||||
32
themes/hextra/layouts/_partials/utils/sort-pages.html
Normal file
32
themes/hextra/layouts/_partials/utils/sort-pages.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{{- $page := .page -}}
|
||||
{{- $by := .by | default "weight" -}}
|
||||
{{- $order := .order | default "asc" -}}
|
||||
|
||||
{{- $pages := slice }}
|
||||
|
||||
{{- if eq $by "weight" }}
|
||||
{{- $pages = $page.Pages.ByWeight }}
|
||||
{{- else if eq $by "date" }}
|
||||
{{- $pages = $page.Pages.ByDate }}
|
||||
{{- else if eq $by "title" }}
|
||||
{{- $pages = $page.Pages.ByTitle }}
|
||||
{{- else if eq $by "expiryDate" }}
|
||||
{{- $pages = $page.Pages.ByExpiryDate }}
|
||||
{{- else if eq $by "publishDate" }}
|
||||
{{- $pages = $page.Pages.ByPublishDate }}
|
||||
{{- else if eq $by "lastmod" }}
|
||||
{{- $pages = $page.Pages.ByLastmod }}
|
||||
{{- else if eq $by "linkTitle" }}
|
||||
{{- $pages = $page.Pages.ByLinkTitle }}
|
||||
{{- else if eq $by "length" }}
|
||||
{{- $pages = $page.Pages.ByLength }}
|
||||
{{- else }}
|
||||
{{- warnf "sort-pages: unknown sort field %q" $by -}}
|
||||
{{- $pages = $page.Pages }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if eq $order "desc" }}
|
||||
{{- $pages = $pages.Reverse }}
|
||||
{{- end -}}
|
||||
|
||||
{{- return $pages -}}
|
||||
19
themes/hextra/layouts/_partials/utils/title.html
Normal file
19
themes/hextra/layouts/_partials/utils/title.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{/*
|
||||
This utility is used to retrieve the title of a page or section.
|
||||
If no title is set, it falls back to using the directory or file name.
|
||||
|
||||
Based on https://github.com/thegeeklab/hugo-geekdoc/blob/v0.44.0/layouts/partials/utils/title.html
|
||||
*/}}
|
||||
{{- $title := "" }}
|
||||
|
||||
{{ if .LinkTitle }}
|
||||
{{ $title = .LinkTitle }}
|
||||
{{ else if .Title }}
|
||||
{{ $title = .Title }}
|
||||
{{ else if and .IsSection .File }}
|
||||
{{ $title = path.Base .File.Dir | humanize | title }}
|
||||
{{ else if and .IsPage .File }}
|
||||
{{ $title = .File.BaseFileName | humanize | title }}
|
||||
{{ end }}
|
||||
|
||||
{{ return $title -}}
|
||||
86
themes/hextra/layouts/_shortcodes/asciinema.html
Normal file
86
themes/hextra/layouts/_shortcodes/asciinema.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{{- /* Get parameters */ -}}
|
||||
{{- $castFile := .Get "file" | default (.Get 0) -}}
|
||||
{{- $theme := .Get "theme" | default "asciinema" -}}
|
||||
{{- $speed := .Get "speed" | default 1 -}}
|
||||
{{- $autoplay := .Get "autoplay" | default false -}}
|
||||
{{- $loop := .Get "loop" | default false -}}
|
||||
{{- $poster := .Get "poster" | default "" -}}
|
||||
{{- $markers := .Get "markers" | default "" -}}
|
||||
|
||||
{{- /* Handle file path: support local files, absolute paths, and remote URLs */ -}}
|
||||
{{- $isLocal := not (urls.Parse $castFile).Scheme -}}
|
||||
{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}}
|
||||
|
||||
{{- if $isLocal -}}
|
||||
{{- /* Local file handling */ -}}
|
||||
{{- $found := false -}}
|
||||
|
||||
{{- /* Try page resources first */ -}}
|
||||
{{- if not $isPage -}}
|
||||
{{- with .Page.Resources.Get $castFile -}}
|
||||
{{- $castFile = .RelPermalink -}}
|
||||
{{- $found = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Try global resources if not found in page resources */ -}}
|
||||
{{- if not $found -}}
|
||||
{{- with resources.Get $castFile -}}
|
||||
{{- $castFile = .RelPermalink -}}
|
||||
{{- $found = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Try static files if not found in resources */ -}}
|
||||
{{- if not $found -}}
|
||||
{{- if hasPrefix $castFile "/" -}}
|
||||
{{- $castFile = relURL (strings.TrimPrefix "/" $castFile) -}}
|
||||
{{- $found = true -}}
|
||||
{{- else -}}
|
||||
{{- /* For relative paths, assume they're in static directory */ -}}
|
||||
{{- $castFile = relURL $castFile -}}
|
||||
{{- $found = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* If still not found, raise an error */ -}}
|
||||
{{- if not $found -}}
|
||||
{{- errorf "Asciinema cast file not found: %s. Please ensure the file exists in your assets, static/, or provide a valid remote URL." $castFile -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Build marker configuration */ -}}
|
||||
{{- $markerConfig := "" -}}
|
||||
{{- if $markers -}}
|
||||
{{- $markerParts := slice -}}
|
||||
{{- range (split $markers ",") -}}
|
||||
{{- $item := trim . " " -}}
|
||||
{{- $colonIndex := findRE ":" $item -}}
|
||||
{{- if $colonIndex -}}
|
||||
{{- /* Marker with label */ -}}
|
||||
{{- $pair := split $item ":" -}}
|
||||
{{- if ge (len $pair) 2 -}}
|
||||
{{- $time := printf "%.1f" (float (trim (index $pair 0) " ")) -}}
|
||||
{{- $label := trim (index $pair 1) " " -}}
|
||||
{{- $markerParts = $markerParts | append (printf "[%s,\"%s\"]" $time $label) -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- /* Simple marker */ -}}
|
||||
{{- $markerParts = $markerParts | append (printf "%.1f" (float $item)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $markerConfig = printf "[%s]" (delimit $markerParts ",") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Mark page as using asciinema */ -}}
|
||||
{{- .Page.Store.Set "hasAsciinema" true -}}
|
||||
|
||||
<div class="asciinema-player"
|
||||
data-cast-file="{{ $castFile }}"
|
||||
data-theme="{{ $theme }}"
|
||||
data-speed="{{ $speed }}"
|
||||
data-autoplay="{{ $autoplay }}"
|
||||
data-loop="{{ $loop }}"
|
||||
{{- if ne $poster "" -}}data-poster="{{ $poster | safeURL }}"{{- end -}}
|
||||
{{- if $markerConfig -}}data-markers="{{ $markerConfig | safeJS }}"{{- end -}}>
|
||||
</div>
|
||||
54
themes/hextra/layouts/_shortcodes/badge.html
Normal file
54
themes/hextra/layouts/_shortcodes/badge.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{{- /*
|
||||
A shortcode to create a badge.
|
||||
|
||||
@param {string} content The content of the badge.
|
||||
@param {string} color The color of the badge.
|
||||
@param {string} class The class of the badge.
|
||||
@param {string} link The link of the badge.
|
||||
@param {string} icon The icon of the badge.
|
||||
|
||||
or
|
||||
|
||||
@param {string} 0 The content of the badge.
|
||||
|
||||
@example {{< badge content="Badge" color="blue" >}}
|
||||
@example {{< badge "Badge" >}}
|
||||
*/ -}}
|
||||
|
||||
{{- if .IsNamedParams -}}
|
||||
{{- $content := .Get "content" -}}
|
||||
{{- $color := .Get "color" | default (.Get "type") | default "" -}}{{- /* Compatibility with previous parameter. */ -}}
|
||||
{{- $class := .Get "class" | default "" -}}
|
||||
{{- $link := .Get "link" | default "" -}}
|
||||
{{- $icon := .Get "icon" | default "" -}}
|
||||
{{- $border := not (eq (.Get "border") false) | default true }}
|
||||
|
||||
{{- if $link -}}
|
||||
<a href="{{ $link }}" title="{{ $content | plainify }}" target="_blank">
|
||||
{{- partial "shortcodes/badge.html" (dict
|
||||
"content" $content
|
||||
"color" $color
|
||||
"class" $class
|
||||
"border" $border
|
||||
"icon" $icon
|
||||
)
|
||||
-}}
|
||||
</a>
|
||||
{{- else -}}
|
||||
{{- partial "shortcodes/badge.html" (dict
|
||||
"content" $content
|
||||
"color" $color
|
||||
"class" $class
|
||||
"border" $border
|
||||
"icon" $icon
|
||||
)
|
||||
-}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- $content := .Get 0 -}}
|
||||
{{- partial "shortcodes/badge.html" (dict
|
||||
"content" $content
|
||||
"border" true
|
||||
)
|
||||
-}}
|
||||
{{- end -}}
|
||||
57
themes/hextra/layouts/_shortcodes/callout.html
Normal file
57
themes/hextra/layouts/_shortcodes/callout.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{{- /*
|
||||
A shortcode to create a callout.
|
||||
|
||||
@param {string} type The type of the callout (default, info, warning, error, important).
|
||||
@param {string} content The content of the callout.
|
||||
@param {string} emoji The emoji of the callout.
|
||||
@param {string} icon The icon of the callout (related to type or can be a custom icon).
|
||||
|
||||
@example {{< callout type="info" >}}Content{{< /callout >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $type := .Get "type" | default "default" -}}
|
||||
{{- $emoji := .Get "emoji" -}}
|
||||
{{- $icon := .Get "icon" -}}
|
||||
|
||||
{{- $styles := newScratch -}}
|
||||
{{- $styles.Set "default" (dict
|
||||
"icon" "light-bulb"
|
||||
"style" "hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "info" (dict
|
||||
"icon" "information-circle"
|
||||
"style" "hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "warning" (dict
|
||||
"icon" "exclamation"
|
||||
"style" "hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "error" (dict
|
||||
"icon" "ban"
|
||||
"style" "hx:border-red-200 hx:bg-red-100 hx:text-red-900 hx:dark:border-red-200/30 hx:dark:bg-red-900/30 hx:dark:text-red-200"
|
||||
)
|
||||
-}}
|
||||
{{- $styles.Set "important" (dict
|
||||
"icon" "exclamation-circle"
|
||||
"style" "hx:border-purple-200 hx:bg-purple-100 hx:text-purple-900 hx:dark:border-purple-200/30 hx:dark:bg-purple-900/30 hx:dark:text-purple-200"
|
||||
)
|
||||
-}}
|
||||
|
||||
{{- $style := or ($styles.Get $type) ($styles.Get "default") -}}
|
||||
|
||||
{{- if and (not $emoji) (not $icon) -}}
|
||||
{{- $icon = $style.icon -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $content := .InnerDeindent | markdownify -}}
|
||||
|
||||
{{- partial "shortcodes/callout.html" (dict
|
||||
"content" $content
|
||||
"emoji" $emoji
|
||||
"icon" $icon
|
||||
"class" $style.style
|
||||
)
|
||||
-}}
|
||||
72
themes/hextra/layouts/_shortcodes/card.html
Normal file
72
themes/hextra/layouts/_shortcodes/card.html
Normal file
@@ -0,0 +1,72 @@
|
||||
{{- /*
|
||||
A shortcode to create a card.
|
||||
|
||||
@param {string} link The link to the card.
|
||||
@param {string} title The title of the card.
|
||||
@param {string} icon The icon of the card.
|
||||
@param {string} subtitle The subtitle of the card.
|
||||
@param {string} tag The tag of the card.
|
||||
@param {string} tagColor The color of the tag.
|
||||
@param {string} image The image of the card.
|
||||
@param {string} alt The alt text for the image (defaults to title if not provided).
|
||||
@param {string} method The method to process the image.
|
||||
@param {string} options The options to process the image.
|
||||
@param {string} imageStyle The style of the image.
|
||||
|
||||
@example {{< card link="/" title="Image Card"
|
||||
}}
|
||||
*/ -}}
|
||||
{{- $link := .Get "link" -}}
|
||||
{{- $title := .Get "title" -}}
|
||||
{{- $icon := .Get "icon" -}}
|
||||
{{- $subtitle := .Get "subtitle" -}}
|
||||
{{- $image := .Get "image" -}}
|
||||
{{- $alt := .Get "alt" | default $title -}}
|
||||
{{- $width := 0 -}}
|
||||
{{- $height := 0 -}}
|
||||
{{- $imageStyle := .Get "imageStyle" -}}
|
||||
{{- $tag := .Get "tag" -}}
|
||||
{{- $tagColor := .Get "tagColor" | default (.Get "tagType") | default "" -}}{{- /* Compatibility with previous parameter. */ -}}
|
||||
{{- $tagBorder := not (eq (.Get "tagBorder") false) | default true }}
|
||||
{{- $tagIcon := .Get "tagIcon" | default "" -}}
|
||||
|
||||
{{/* Image processing options */}}
|
||||
{{- $method := .Get "method" | default "Resize" | humanize -}}
|
||||
{{- $options := .Get "options" | default "800x webp q80" -}}
|
||||
{{- $process := .Get "process" | default (printf "%s %s" $method $options) -}}
|
||||
|
||||
{{- if and $image (not (urls.Parse $image).Scheme) -}}
|
||||
{{- with or (.Page.Resources.Get $image) (resources.Get $image) -}}
|
||||
{{/* .Process does not work on svgs */}}
|
||||
{{- if (not (eq .MediaType.SubType "svg")) -}}
|
||||
{{/* Retrieve the $image resource from local or global resources */}}
|
||||
{{- $processed := .Process $process -}}
|
||||
{{- $width = $processed.Width -}}
|
||||
{{- $height = $processed.Height -}}
|
||||
{{- $image = $processed.RelPermalink -}}
|
||||
{{- end -}}
|
||||
{{ else }}
|
||||
{{/* Otherwise, use relative link of the image */}}
|
||||
{{- if hasPrefix $image "/" -}}
|
||||
{{- $image = relURL (strings.TrimPrefix "/" $image) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- partial "shortcodes/card" (dict
|
||||
"page" .Page
|
||||
"link" $link
|
||||
"title" $title
|
||||
"icon" $icon
|
||||
"subtitle" $subtitle
|
||||
"image" $image
|
||||
"alt" $alt
|
||||
"width" $width
|
||||
"height" $height
|
||||
"imageStyle" $imageStyle
|
||||
"tag" $tag
|
||||
"tagType" $tagColor
|
||||
"tagBorder" $tagBorder
|
||||
"tagIcon" $tagIcon
|
||||
)
|
||||
-}}
|
||||
11
themes/hextra/layouts/_shortcodes/cards.html
Normal file
11
themes/hextra/layouts/_shortcodes/cards.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{{- /*
|
||||
A shortcode for creating cards.
|
||||
|
||||
@param {string} cols The number of columns.
|
||||
|
||||
@example {{< cards cols="3" >}}{{< /cards >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $cols := .Get "cols" | default 3 -}}
|
||||
|
||||
{{- partial "shortcodes/cards" (dict "cols" $cols "content" .Inner) -}}
|
||||
20
themes/hextra/layouts/_shortcodes/details.html
Normal file
20
themes/hextra/layouts/_shortcodes/details.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{- /*
|
||||
A built-in component to display a collapsible content.
|
||||
|
||||
@param {string} title The title of the details.
|
||||
@param {string} closed Whether the details are closed or not (default: false).
|
||||
|
||||
@example {{% details title="Details" %}}Content{{% /details %}}
|
||||
*/ -}}
|
||||
|
||||
{{- $title := .Get "title" | default "" -}}
|
||||
{{- $closed := eq (.Get "closed") "true" | default false -}}
|
||||
|
||||
<details class="hx:last-of-type:mb-0 hx:rounded-lg hx:bg-neutral-50 hx:dark:bg-neutral-800 hx:p-2 hx:mt-4 hx:group" {{ if not $closed }}open{{ end }}>
|
||||
<summary class="hx:flex hx:items-center hx:cursor-pointer hx:select-none hx:list-none hx:p-1 hx:rounded-sm hx:transition-colors hx:hover:bg-gray-100 hx:dark:hover:bg-neutral-800 hx:before:mr-1 hx:before:inline-block hx:before:transition-transform hx:before:content-[''] hx:dark:before:invert hx:rtl:before:rotate-180 hx:group-open:before:rotate-90">
|
||||
<strong class="hx:text-lg">{{ $title | markdownify }}</strong>
|
||||
</summary>
|
||||
<div class="hx:p-2 hx:overflow-hidden">
|
||||
{{ .InnerDeindent | markdownify }}
|
||||
</div>
|
||||
</details>
|
||||
11
themes/hextra/layouts/_shortcodes/filetree/container.html
Normal file
11
themes/hextra/layouts/_shortcodes/filetree/container.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{{- /*
|
||||
A file tree container.
|
||||
|
||||
@example {{< filetree/container >}}{{< /filetree/container >}}
|
||||
*/ -}}
|
||||
|
||||
<div class="hextra-filetree hx:mt-6 hx:select-none hx:text-sm hx:text-gray-800 hx:dark:text-gray-300 not-prose">
|
||||
<div class="hx:inline-block hx:rounded-lg hx:px-4 hx:py-2 hx:border hx:border-gray-200 hx:dark:border-neutral-800">
|
||||
{{- .InnerDeindent -}}
|
||||
</div>
|
||||
</div>
|
||||
16
themes/hextra/layouts/_shortcodes/filetree/file.html
Normal file
16
themes/hextra/layouts/_shortcodes/filetree/file.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{{- /*
|
||||
A file in a file tree.
|
||||
|
||||
@param {string} name The name of the file.
|
||||
|
||||
@example {{< filetree/file name="_index.md" >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $name := .Get "name" -}}
|
||||
|
||||
<li class="hx:flex hx:list-none">
|
||||
<span class="hx:inline-flex hx:cursor-default hx:items-center hx:py-1">
|
||||
{{- partial "utils/icon" (dict "name" "document-text" "attributes" "width=1em") -}}
|
||||
<span class="hx:ltr:ml-1 hx:rtl:mr-1">{{ $name | markdownify }}</span>
|
||||
</span>
|
||||
</li>
|
||||
26
themes/hextra/layouts/_shortcodes/filetree/folder.html
Normal file
26
themes/hextra/layouts/_shortcodes/filetree/folder.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{{- /*
|
||||
A folder in a file tree.
|
||||
|
||||
@param {string} name The name of the folder.
|
||||
@param {string} state The state of the folder.
|
||||
|
||||
@example {{< filetree/folder name="docs" state="closed" >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $name := .Get "name" -}}
|
||||
{{- $state := .Get "state" | default "open" }}
|
||||
|
||||
<li class="hx:group hx:flex hx:list-none hx:flex-col">
|
||||
<button class="hextra-filetree-folder hx:inline-flex hx:cursor-pointer hx:items-center hx:py-1 hx:hover:opacity-60">
|
||||
<span data-state="{{ $state }}" class="hx:data-[state=open]:hidden">
|
||||
{{- partial "utils/icon" (dict "name" "folder" "attributes" "width=1em") -}}
|
||||
</span>
|
||||
<span data-state="{{ $state }}" class="hx:data-[state=closed]:hidden">
|
||||
{{- partial "utils/icon" (dict "name" "folder-open" "attributes" "width=1em") -}}
|
||||
</span>
|
||||
<span class="hx:ltr:ml-1 hx:rtl:mr-1">{{ $name }}</span>
|
||||
</button>
|
||||
<ul data-state="{{ $state }}" class="hx:ltr:pl-5 hx:rtl:pr-5 hx:data-[state=closed]:hidden">
|
||||
{{- .InnerDeindent -}}
|
||||
</ul>
|
||||
</li>
|
||||
51
themes/hextra/layouts/_shortcodes/hextra/feature-card.html
Normal file
51
themes/hextra/layouts/_shortcodes/hextra/feature-card.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{{- /*
|
||||
A shortcode for displaying a feature card.
|
||||
|
||||
@param {string} title The title of the card.
|
||||
@param {string} subtitle The subtitle of the card.
|
||||
@param {string} class The class of the card.
|
||||
@param {string} image The image of the card.
|
||||
@param {string} imageClass The class of the image.
|
||||
@param {string} style The style of the card.
|
||||
@param {string} icon The icon of the card.
|
||||
@param {string} link The link of the card.
|
||||
|
||||
@example {{< hextra/feature-card title="Feature Card" subtitle="This is a feature card." >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $title := .Get "title" -}}
|
||||
{{- $subtitle := .Get "subtitle" -}}
|
||||
{{- $class := .Get "class" -}}
|
||||
{{- $image := .Get "image" -}}
|
||||
{{- $imageClass := .Get "imageClass" -}}
|
||||
{{- $style := .Get "style" -}}
|
||||
{{- $icon := .Get "icon" -}}
|
||||
{{- $link := .Get "link" -}}
|
||||
|
||||
{{- $external := hasPrefix $link "http" -}}
|
||||
{{- $href := cond (strings.HasPrefix $link "/") ($link | relURL) $link -}}
|
||||
|
||||
{{- if hasPrefix $image "/" -}}
|
||||
{{- $image = relURL (strings.TrimPrefix "/" $image) -}}
|
||||
{{- end -}}
|
||||
|
||||
<a
|
||||
{{ with $link }}href="{{ $href }}" {{ with $external }} target="_blank" rel="noreferrer"{{ end }}{{ end }}
|
||||
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
|
||||
class="{{ $class }} hextra-feature-card not-prose hx:block hx:relative hx:overflow-hidden hx:rounded-3xl hx:border hx:border-gray-200 hx:hover:border-gray-300 hx:dark:border-neutral-800 hx:dark:hover:border-neutral-700 hx:before:pointer-events-none hx:before:absolute hx:before:inset-0 hx:before:bg-glass-gradient"
|
||||
>
|
||||
<div class="hx:relative hx:w-full hx:p-6">
|
||||
<h3 class="hx:text-2xl hx:font-medium hx:leading-6 hx:mb-2 hx:flex hx:items-center">
|
||||
{{ with $icon -}}
|
||||
<span class="hx:pr-2">
|
||||
{{- partial "utils/icon.html" (dict "name" . "attributes" "height=1.5rem") -}}
|
||||
</span>
|
||||
{{ end -}}
|
||||
<span>{{ $title }}</span>
|
||||
</h3>
|
||||
<p class="hx:text-gray-500 hx:dark:text-gray-400 hx:text-sm hx:leading-6">{{ $subtitle | markdownify }}</p>
|
||||
</div>
|
||||
{{- with $image -}}
|
||||
<img src="{{ . }}" class="hx:absolute hx:max-w-none {{ $imageClass }}" alt="{{ $title }}" />
|
||||
{{- end -}}
|
||||
</a>
|
||||
21
themes/hextra/layouts/_shortcodes/hextra/feature-grid.html
Normal file
21
themes/hextra/layouts/_shortcodes/hextra/feature-grid.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- /*
|
||||
A shortcode for displaying a feature grid.
|
||||
|
||||
@param {string} cols The number of columns.
|
||||
@param {string} style The style of the grid.
|
||||
|
||||
@example {{< hextra/feature-grid cols="3" >}}{{< /hextra/feature-grid >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $cols := .Get "cols" | default 3 -}}
|
||||
{{- $style := .Get "style" | default "" -}}
|
||||
|
||||
{{- $css := printf "--hextra-feature-grid-cols: %v; %s" $cols $style -}}
|
||||
|
||||
|
||||
<div
|
||||
class="hextra-feature-grid hx:grid hx:sm:max-lg:grid-cols-2 hx:max-sm:grid-cols-1 hx:gap-4 hx:w-full not-prose"
|
||||
{{ with $css }}style="{{ . | safeCSS }}"{{ end }}
|
||||
>
|
||||
{{ .Inner }}
|
||||
</div>
|
||||
24
themes/hextra/layouts/_shortcodes/hextra/hero-badge.html
Normal file
24
themes/hextra/layouts/_shortcodes/hextra/hero-badge.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{{- /*
|
||||
A shortcode for rendering a badge with a link.
|
||||
|
||||
@param {string} link The link of the badge.
|
||||
@param {string} class The class of the badge.
|
||||
@param {string} style The style of the badge.
|
||||
|
||||
@example {{< hextra/hero-badge >}}{{< /hextra/hero-badge >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $link := .Get "link" -}}
|
||||
{{- $external := hasPrefix $link "http" -}}
|
||||
{{- $href := cond (hasPrefix $link "/") ($link | relURL) $link -}}
|
||||
{{- $class := .Get "class" }}
|
||||
{{- $style := .Get "style" -}}
|
||||
|
||||
<a
|
||||
{{ if $link }}href="{{ $href }}"{{ end }}
|
||||
class="{{ $class }} not-prose hx:inline-flex hx:items-center hx:rounded-full hx:gap-2 hx:px-3 hx:py-1 hx:text-xs hx:text-gray-600 hx:dark:text-gray-400 hx:bg-gray-100 hx:dark:bg-neutral-800 hx:border-gray-200 hx:dark:border-neutral-800 hx:border hx:hover:border-gray-400 hx:dark:hover:text-gray-50 hx:dark:hover:border-gray-600 hx:transition-all hx:ease-in hx:duration-200"
|
||||
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
|
||||
{{ if $external }}target="_blank" rel="noreferrer"{{ end -}}
|
||||
>
|
||||
{{ .Inner | markdownify }}
|
||||
</a>
|
||||
25
themes/hextra/layouts/_shortcodes/hextra/hero-button.html
Normal file
25
themes/hextra/layouts/_shortcodes/hextra/hero-button.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{{- /*
|
||||
A shortcode for rendering a button with a link.
|
||||
|
||||
@param {string} link The link of the button.
|
||||
@param {string} text The text of the button.
|
||||
@param {string} style The style of the button.
|
||||
|
||||
@example {{< hextra/hero-button text="Get Started" link="docs" >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $link := .Get "link" -}}
|
||||
{{- $text := .Get "text" -}}
|
||||
{{- $style := .Get "style" -}}
|
||||
|
||||
{{- $external := hasPrefix $link "http" -}}
|
||||
{{- $href := cond (hasPrefix $link "/") ($link | relURL) $link -}}
|
||||
|
||||
<a
|
||||
href="{{ $href }}"
|
||||
class="not-prose hx:font-medium hx:cursor-pointer hx:px-6 hx:py-3 hx:rounded-full hx:text-center hx:text-white hx:inline-block hx:bg-primary-600 hx:hover:bg-primary-700 hx:focus:outline-hidden hx:focus:ring-4 hx:focus:ring-primary-300 hx:dark:bg-primary-600 hx:dark:hover:bg-primary-700 hx:dark:focus:ring-primary-800 hx:transition-all hx:ease-in hx:duration-200"
|
||||
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
|
||||
{{ if $external }}target="_blank" rel="noreferrer"{{ end -}}
|
||||
>
|
||||
{{- $text -}}
|
||||
</a>
|
||||
56
themes/hextra/layouts/_shortcodes/hextra/hero-container.html
Normal file
56
themes/hextra/layouts/_shortcodes/hextra/hero-container.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{{- /*
|
||||
A simple hero container with an image on the left side.
|
||||
|
||||
@param {string} class The class of the container.
|
||||
@param {string} cols The number of columns (default: 2).
|
||||
@param {string} image The image of the container.
|
||||
@param {bool} imageCard Whether to display the image as a card (default: false).
|
||||
@param {string} imageClass The class of the image.
|
||||
@param {string} imageLink The link of the image.
|
||||
@param {string} imageStyle The style of the image.
|
||||
@param {string} imageTitle The title of the image.
|
||||
@param {int} imageWidth The width of the image (default: 350).
|
||||
@param {int} imageHeight The height of the image (default: 350).
|
||||
@param {string} style The style of the container.
|
||||
|
||||
@example {{< hextra/hero-container image="image.png" imageLink="https://example.com" imageTitle="Example Image" >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $class := .Get "class" -}}
|
||||
{{- $cols := .Get "cols" | default 2 -}}
|
||||
{{- $image := .Get "image" -}}
|
||||
{{- $imageCard := .Get "imageCard" | default false -}}
|
||||
{{- $imageClass := .Get "imageClass" -}}
|
||||
{{- $imageLink := .Get "imageLink" -}}
|
||||
{{- $imageLinkExternal := hasPrefix $imageLink "http" -}}
|
||||
{{- $imageStyle := .Get "imageStyle" -}}
|
||||
{{- $imageTitle := .Get "imageTitle" -}}
|
||||
{{- $imageWidth := .Get "imageWidth" | default 350 -}}
|
||||
{{- $imageHeight := .Get "imageHeight" | default 350 -}}
|
||||
{{- $style := .Get "style" -}}
|
||||
|
||||
{{- $css := printf "--hextra-feature-grid-cols: %v; %s" $cols $style -}}
|
||||
{{- $href := cond (hasPrefix $imageLink "/") ($imageLink | relURL) $imageLink -}}
|
||||
{{- if hasPrefix $image "/" -}}
|
||||
{{- $image = relURL (strings.TrimPrefix "/" $image) -}}
|
||||
{{- end -}}
|
||||
|
||||
<div
|
||||
class="{{ $class }} hextra-feature-grid hx:grid hx:sm:max-lg:grid-cols-2 hx:max-sm:grid-cols-1 hx:gap-4 hx:w-full not-prose"
|
||||
{{ with $css }}style="{{ . | safeCSS }}"{{ end }}
|
||||
>
|
||||
<div class="hx:w-full">
|
||||
{{ .Inner }}
|
||||
</div>
|
||||
{{- with $image }}
|
||||
<div class="hx:mx-auto">
|
||||
<a
|
||||
{{ with $imageLink }}href="{{ $href }}" {{ with $imageLinkExternal }} target="_blank" rel="noreferrer"{{ end }}{{ end }}
|
||||
{{ with $imageStyle }}style="{{ . | safeCSS }}"{{ end }}
|
||||
class="{{ $imageClass }} {{ if $imageCard }}hextra-feature-card not-prose hx:block hx:relative hx:p-6 hx:overflow-hidden hx:rounded-3xl hx:border hx:border-gray-200 hx:hover:border-gray-300 hx:dark:border-neutral-800 hx:dark:hover:border-neutral-700 hx:before:pointer-events-none hx:before:absolute hx:before:inset-0 hx:before:bg-glass-gradient{{ end }}"
|
||||
>
|
||||
<img src="{{ $image }}" width="{{ $imageWidth }}" height="{{ $imageHeight }}" {{ with $imageTitle }}alt="{{ $imageTitle }}"{{ end }}/>
|
||||
</a>
|
||||
</div>
|
||||
{{ end -}}
|
||||
</div>
|
||||
16
themes/hextra/layouts/_shortcodes/hextra/hero-headline.html
Normal file
16
themes/hextra/layouts/_shortcodes/hextra/hero-headline.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{{- /*
|
||||
A shortcode for displaying a hero headline.
|
||||
|
||||
@param {string} style The style of the headline.
|
||||
|
||||
@example {{< hextra/hero-headline >}}{{< /hextra/hero-headline >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $style := .Get "style" -}}
|
||||
|
||||
<h1
|
||||
class="not-prose hx:text-4xl hx:font-bold hx:leading-none hx:tracking-tighter hx:md:text-5xl hx:py-2 hx:bg-clip-text hx:text-transparent hx:bg-gradient-to-r hx:from-gray-900 hx:to-gray-600 hx:dark:from-gray-100 hx:dark:to-gray-400"
|
||||
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
|
||||
>
|
||||
{{ .Inner | markdownify }}
|
||||
</h1>
|
||||
20
themes/hextra/layouts/_shortcodes/hextra/hero-section.html
Normal file
20
themes/hextra/layouts/_shortcodes/hextra/hero-section.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{- /*
|
||||
A simple hero section with a heading and optional style.
|
||||
|
||||
@param {string} heading The heading level (default: h2).
|
||||
@param {string} style The style of the heading.
|
||||
@param {string} content The content of the heading.
|
||||
|
||||
@example {{< hextra/hero-section heading="h3" >}}{{< /hextra/hero-section >}}>
|
||||
*/ -}}
|
||||
|
||||
{{- $style := .Get "style" -}}
|
||||
{{- $heading := int (strings.TrimPrefix "h" (.Get "heading" | default "h2")) -}}
|
||||
{{- $size := cond (ge $heading 4) "xl" (cond (eq $heading 3) "2xl" "4xl") -}}
|
||||
|
||||
<h{{ $heading }}
|
||||
class="not-prose hx:text-{{ $size }} hx:font-bold hx:leading-none hx:tracking-tighter hx:md:text-3xl hx:py-2 hx:bg-clip-text hx:text-transparent hx:bg-gradient-to-r hx:from-gray-900 hx:to-gray-600 hx:dark:from-gray-100 hx:dark:to-gray-400"
|
||||
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
|
||||
>
|
||||
{{ .Inner | markdownify }}
|
||||
</h{{ $heading }}>
|
||||
16
themes/hextra/layouts/_shortcodes/hextra/hero-subtitle.html
Normal file
16
themes/hextra/layouts/_shortcodes/hextra/hero-subtitle.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{{- /*
|
||||
A shortcode for displaying a hero subtitle.
|
||||
|
||||
@param {string} style The style of the subtitle.
|
||||
|
||||
@example {{< hextra/hero-subtitle >}}{{< /hextra/hero-subtitle >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $style := .Get "style" -}}
|
||||
|
||||
<p
|
||||
class="not-prose hx:text-xl hx:text-gray-600 hx:dark:text-gray-400 hx:sm:text-xl"
|
||||
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
|
||||
>
|
||||
{{ .Inner | markdownify }}
|
||||
</p>
|
||||
27
themes/hextra/layouts/_shortcodes/icon.html
Normal file
27
themes/hextra/layouts/_shortcodes/icon.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{{- /*
|
||||
Create an icon.
|
||||
|
||||
@param {string} name The name of the icon.
|
||||
@param {string} attributes The attributes of the icon.
|
||||
|
||||
or
|
||||
|
||||
@param {string} 0 The name of the icon.
|
||||
|
||||
@example {{< icon name="github" >}}
|
||||
@example {{< icon "github" >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $name := .Get "name" | default (.Get 0) -}}
|
||||
{{- $icon := index site.Data.icons $name -}}
|
||||
{{- $attributes := .Get "attributes" | default "height=1em"}}
|
||||
|
||||
{{- if not $icon -}}
|
||||
{{ errorf "icon %q not found" $name }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $icon = replaceRE "<svg" (printf "<svg %s" $attributes) $icon -}}
|
||||
|
||||
<span class="hx:inline-block hx:align-text-bottom hextra-icon">
|
||||
{{- $icon | safeHTML -}}
|
||||
</span>
|
||||
22
themes/hextra/layouts/_shortcodes/include.html
Normal file
22
themes/hextra/layouts/_shortcodes/include.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{{- /*
|
||||
https://github.com/gohugoio/gohugoioTheme/blob/master/layouts/shortcodes/include.html
|
||||
|
||||
Renders the page using the RenderShortcode method on the Page object.
|
||||
|
||||
You must call this shortcode using the {{% %}} notation.
|
||||
|
||||
@param {string} (positional parameter 0) The path to the page, relative to the content directory.
|
||||
@returns template.HTML
|
||||
|
||||
@example {{% include "functions/_common/glob-patterns" %}}
|
||||
*/}}
|
||||
|
||||
{{- with .Get 0 }}
|
||||
{{- with site.GetPage . }}
|
||||
{{- .RenderShortcodes }}
|
||||
{{- else }}
|
||||
{{- errorf "The %q shortcode was unable to find %q. See %s" $.Name . $.Position }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- errorf "The %q shortcode requires a positional parameter indicating the path of the file to include. See %s" .Name .Position }}
|
||||
{{- end }}
|
||||
88
themes/hextra/layouts/_shortcodes/jupyter.html
Normal file
88
themes/hextra/layouts/_shortcodes/jupyter.html
Normal file
@@ -0,0 +1,88 @@
|
||||
{{- /*
|
||||
Render Jupyter Notebook
|
||||
|
||||
@param {string} 0 The path of the Jupyter Notebook.
|
||||
|
||||
@example {{% jupyter "notebook.ipynb" %}}
|
||||
*/ -}}
|
||||
|
||||
{{- $path := .Get 0 -}}
|
||||
{{- $data := "" -}}
|
||||
{{- $page := .Page -}}
|
||||
|
||||
{{- $isLocal := not (urls.Parse $path).Scheme -}}
|
||||
{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}}
|
||||
|
||||
{{/* https://gohugo.io/functions/transform/unmarshal/ */}}
|
||||
{{- if (not $isLocal) -}}
|
||||
{{- with resources.GetRemote $path -}}
|
||||
{{- with unmarshal .Content -}}{{- $data = . -}}{{- end -}}
|
||||
{{- else -}}
|
||||
{{- errorf "Remote resource not found: %s" $path -}}
|
||||
{{- end -}}
|
||||
{{- else if (not $isPage) -}}
|
||||
{{- with .Page.Resources.Get $path -}}
|
||||
{{- with unmarshal .Content -}}{{- $data = . -}}{{- end -}}
|
||||
{{- else -}}
|
||||
{{- errorf "Local resource not found: %s" $path -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- with resources.Get $path -}}
|
||||
{{- with unmarshal .Content -}}{{- $data = . -}}{{- end -}}
|
||||
{{- else -}}
|
||||
{{- errorf "Local resource not found: %s" $path -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $language := index $data "metadata" "language_info" "name" | default "python" -}}
|
||||
|
||||
{{- with index $data "cells" -}}
|
||||
{{- range $cell := . -}}
|
||||
{{- if eq (index $cell "cell_type") "code" -}}
|
||||
{{- $source := index $cell "source" -}}
|
||||
{{- $sourceContent := (cond (reflect.IsSlice $source) (delimit $source "") $source) -}}
|
||||
{{- with ($sourceContent | strings.Chomp) -}}
|
||||
{{ (printf "\n\n```%s\n%s\n```\n" $language .) | safeHTML -}}
|
||||
{{- end -}}
|
||||
|
||||
<div class="hextra-jupyter-code-cell hextra-scrollbar">
|
||||
{{- $outputs := index $cell "outputs" -}}
|
||||
{{- with $outputs -}}
|
||||
<div class="hextra-jupyter-code-cell-outputs-container">
|
||||
<div class="hextra-jupyter-code-cell-outputs">
|
||||
{{- range $output := . -}}
|
||||
{{- if eq (index $output "output_type") "display_data" -}}
|
||||
{{- $data := index $output "data" -}}
|
||||
{{- $image := index $data "image/png" -}}
|
||||
{{- if $image -}}
|
||||
<img src="data:image/png;base64,{{- $image -}}" alt="image" />
|
||||
{{- end -}}
|
||||
{{- else if eq (index $output "output_type") "stream" -}}
|
||||
{{- $text := index $output "text" -}}
|
||||
{{- $textContent := (cond (reflect.IsSlice $text) (delimit $text "") $text) -}}
|
||||
<pre class="not-prose">{{- $textContent -}}</pre>
|
||||
{{- else if eq (index $output "output_type") "execute_result" -}}
|
||||
{{- $data := index $output "data" -}}
|
||||
{{- $text := index $data "text/plain" -}}
|
||||
{{- $textContent := (cond (reflect.IsSlice $text) (delimit $text "") $text) -}}
|
||||
<pre class="not-prose">{{- $textContent -}}</pre>
|
||||
{{- $html := index $data "text/html" -}}
|
||||
{{- if $html -}}
|
||||
{{- $htmlText := delimit $html "" -}}
|
||||
<div>
|
||||
{{- $htmlText | safeHTML -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- else if eq (index $cell "cell_type") "markdown" -}}
|
||||
{{- $source := index $cell "source" }}
|
||||
{{- $sourceContent := (cond (reflect.IsSlice $source) (delimit $source "") $source) }}
|
||||
{{ (printf "\n%s\n" $sourceContent) | safeHTML }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
15
themes/hextra/layouts/_shortcodes/pdf.html
Normal file
15
themes/hextra/layouts/_shortcodes/pdf.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{{- /*
|
||||
Shortcode to include a PDF file in a page.
|
||||
|
||||
@param {string} 0 The path to the PDF file.
|
||||
|
||||
@example {{< pdf "path/to/file.pdf" >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $path := .Get 0 -}}
|
||||
{{- $url := partial "utils/file-path" (dict "page" .Page "path" $path) -}}
|
||||
|
||||
|
||||
<div class="hextra-pdf">
|
||||
<iframe src="{{ $url | safeURL }}" width="100%" style="min-height: 32rem;" frameborder="0"></iframe>
|
||||
</div>
|
||||
9
themes/hextra/layouts/_shortcodes/steps.html
Normal file
9
themes/hextra/layouts/_shortcodes/steps.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{{- /*
|
||||
A shortcode for creating a step list.
|
||||
|
||||
@example {{% steps %}}{{% /steps %}}
|
||||
*/ -}}
|
||||
|
||||
<div class="hextra-steps hx:ml-4 hx:mb-12 hx:ltr:border-l hx:rtl:border-r hx:border-gray-200 hx:ltr:pl-6 hx:rtl:pr-6 hx:dark:border-neutral-800 [counter-reset:step]">
|
||||
{{- .Inner -}}
|
||||
</div>
|
||||
24
themes/hextra/layouts/_shortcodes/tab.html
Normal file
24
themes/hextra/layouts/_shortcodes/tab.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{{- /*
|
||||
Create a tab.
|
||||
|
||||
@param {string} name The name of the tab.
|
||||
@param {string} selected Whether the tab is selected.
|
||||
|
||||
@example {{< tab name="Foo" selected=true >}}content{{< /tab >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $name := .Get "name" | default (printf "Tab %d" .Ordinal) -}}
|
||||
|
||||
{{- $selected := .Get "selected" -}}
|
||||
{{- if .Parent.Get "defaultIndex" -}}
|
||||
{{- $selected = eq .Ordinal (int (.Parent.Get "defaultIndex")) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $tabs := .Parent.Store.Get "tabs" | default slice -}}
|
||||
{{ .Parent.Store.Set "tabs" ($tabs | append (dict
|
||||
"id" .Ordinal
|
||||
"name" $name
|
||||
"content" .InnerDeindent
|
||||
"selected" $selected
|
||||
))
|
||||
-}}
|
||||
39
themes/hextra/layouts/_shortcodes/tabs.html
Normal file
39
themes/hextra/layouts/_shortcodes/tabs.html
Normal file
@@ -0,0 +1,39 @@
|
||||
{{- /*
|
||||
Create a tabbed interface with the given items.
|
||||
|
||||
@example {{< tabs >}}...{{< /tabs >}}
|
||||
*/ -}}
|
||||
|
||||
{{- /* Unused, but required for the shortcode to work. */ -}}
|
||||
{{- .Inner -}}
|
||||
|
||||
{{- /* Enable syncing of tabs across the page. */ -}}
|
||||
{{- $enableSync := false -}}
|
||||
{{- if or (eq .Page.Params.tabs.sync false) (eq .Page.Params.tabs.sync true) -}}
|
||||
{{- $enableSync = .Page.Params.tabs.sync -}}
|
||||
{{- else -}}
|
||||
{{- $enableSync = site.Params.page.tabs.sync | default false -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $tabs := ($.Store.Get "tabs") | default slice -}}
|
||||
|
||||
{{- /* Compatibility with previous parameter "items". */ -}}
|
||||
{{- if .Get "defaultIndex" -}}
|
||||
{{- warnf "The 'defaultIndex' parameter of the 'tabs' shortcode is deprecated. Please use 'selected' on 'tab' instead." -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .Get "items" -}}
|
||||
{{- warnf "The 'items' parameter of the 'tabs' shortcode is deprecated. Please use 'name' on 'tab' instead." -}}
|
||||
|
||||
{{- $items := split (.Get "items") "," -}}
|
||||
|
||||
{{- $temp := slice -}}
|
||||
{{- range $i, $item := $items -}}
|
||||
{{- $tab := index $tabs $i -}}
|
||||
{{- $temp = $temp | append (merge $tab (dict "name" $item)) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $tabs = $temp -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- partial "shortcodes/tabs" (dict "tabs" $tabs "enableSync" $enableSync "id" .Ordinal) -}}
|
||||
13
themes/hextra/layouts/baseof.html
Normal file
13
themes/hextra/layouts/baseof.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Site.Language.Lang }}" dir="{{ .Site.Language.LanguageDirection | default `ltr` }}">
|
||||
{{- partial "head.html" . -}}
|
||||
<body>
|
||||
{{- partial "banner.html" . -}}
|
||||
{{- partial "navbar.html" . -}}
|
||||
{{- block "main" . }}{{ end -}}
|
||||
{{- if or (eq .Site.Params.footer.enable nil) (.Site.Params.footer.enable) }}
|
||||
{{ partial "footer.html" . }}
|
||||
{{ end }}
|
||||
{{ partial "scripts.html" . }}
|
||||
</body>
|
||||
</html>
|
||||
39
themes/hextra/layouts/blog/list.html
Normal file
39
themes/hextra/layouts/blog/list.html
Normal file
@@ -0,0 +1,39 @@
|
||||
{{ define "main" }}
|
||||
{{- $readMore := (T "readMore") | default "Read more →" -}}
|
||||
<div class="hx:mx-auto hx:flex hextra-max-page-width">
|
||||
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
|
||||
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
|
||||
{{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
|
||||
<br class="hx:mt-1.5 hx:text-sm" />
|
||||
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
|
||||
<div class="content">{{ .Content }}</div>
|
||||
{{- $pages := partial "utils/sort-pages" (dict "page" . "by" site.Params.blog.list.sortBy "order" site.Params.blog.list.sortOrder) -}}
|
||||
{{- $pagerSize := site.Params.blog.list.pagerSize | default 10 -}}
|
||||
{{- $paginator := .Paginate $pages $pagerSize -}}
|
||||
{{- range $paginator.Pages }}
|
||||
<div class="hx:mb-10">
|
||||
<h3><a style="color: inherit; text-decoration: none;" class="hx:block hx:font-semibold hx:mt-8 hx:text-2xl " href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
|
||||
{{ if site.Params.blog.list.displayTags }}
|
||||
<div class="hx:text-sm hx:leading-7">
|
||||
{{ partial "tags.html" (dict "context" .) }}
|
||||
</div>
|
||||
{{ end }}
|
||||
<p class="hx:opacity-80 hx:mt-4 hx:leading-7">{{- partial "utils/page-description" . -}}</p>
|
||||
<p class="hx:opacity-80 hx:mt-1 hx:leading-7">
|
||||
<a class="hx:text-[color:hsl(var(--primary-hue),100%,50%)] hx:underline hx:underline-offset-2 hx:decoration-from-font" href="{{ .RelPermalink }}">
|
||||
{{- $readMore -}}
|
||||
</a>
|
||||
</p>
|
||||
<p class="hx:opacity-50 hx:text-sm hx:mt-4 hx:leading-7">{{ partial "utils/format-date" .Date }}</p>
|
||||
</div>
|
||||
{{ end -}}
|
||||
|
||||
{{- if gt $paginator.TotalPages 1 -}}
|
||||
{{ partial "components/blog-pager.html" $paginator }}
|
||||
{{- end -}}
|
||||
</main>
|
||||
</article>
|
||||
<div class="hx:max-xl:hidden hx:h-0 hx:w-64 hx:shrink-0"></div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
50
themes/hextra/layouts/blog/single.html
Normal file
50
themes/hextra/layouts/blog/single.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{{ define "main" }}
|
||||
<div class="hx:mx-auto hx:flex hextra-max-page-width">
|
||||
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
|
||||
{{ partial "toc.html" . }}
|
||||
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
|
||||
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
|
||||
{{ if .Title }}<h1 class="hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
|
||||
<div class="hx:mt-4 hx:mb-16 hx:text-gray-500 hx:dark:text-gray-400 hx:text-sm hx:flex hx:items-center hx:flex-wrap hx:gap-y-2">
|
||||
{{- with $date := .Date }}<span class="hx:mr-1">{{ partial "utils/format-date" $date }}</span>{{ end -}}
|
||||
{{- $lazyLoading := site.Params.enableImageLazyLoading | default true -}}
|
||||
{{ if and .Date .Params.authors }}<span class="hx:mx-1">·</span>{{ end -}}
|
||||
{{- with $.Params.authors -}}
|
||||
{{- range $i, $author := . -}}
|
||||
{{- if reflect.IsMap $author -}}
|
||||
{{- if and $i (not $author.image) }}<span class="hx:mr-1">,</span>{{ end -}}
|
||||
<a
|
||||
{{ with $author.link }}href="{{ . }}" target="_blank"{{ end }}
|
||||
class="hx:group hx:inline-flex hx:items-center hx:text-current hx:gap-x-1.5 hx:mx-1"
|
||||
{{ with $author.name }}title="{{ . }}"{{ end }}
|
||||
>
|
||||
{{- with $image := $author.image }}
|
||||
{{- $isLocal := not (urls.Parse $image).Scheme -}}
|
||||
{{- $startsWithSlash := hasPrefix $image "/" -}}
|
||||
{{- if and $isLocal $startsWithSlash }}
|
||||
{{- $image = (relURL (strings.TrimPrefix "/" $image)) -}}
|
||||
{{ end -}}
|
||||
<img src="{{ $image | safeURL }}" alt="{{ $author.name }}" class="hx:inline-block hx:h-4 hx:w-4 hx:rounded-full" {{ if $lazyLoading }}loading="lazy"{{ end }} />
|
||||
{{ end -}}
|
||||
<div class="hx:group-hover:underline">{{ $author.name }}</div>
|
||||
</a>
|
||||
{{- else -}}
|
||||
{{- if $i }}<span class="hx:mr-1">,</span>{{ end -}}<span class="hx:mx-1">{{ $author }}</span>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
<div class="content">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
{{- partial "components/last-updated.html" . -}}
|
||||
{{- if (site.Params.blog.article.displayPagination | default true) -}}
|
||||
{{- .Store.Set "reversePagination" (.Params.reversePagination | default true) -}}
|
||||
{{- partial "components/pager.html" . -}}
|
||||
{{ end }}
|
||||
{{- partial "components/comments.html" . -}}
|
||||
</main>
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
18
themes/hextra/layouts/docs/list.html
Normal file
18
themes/hextra/layouts/docs/list.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{ define "main" }}
|
||||
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
|
||||
{{ partial "sidebar.html" (dict "context" .) }}
|
||||
{{ partial "toc.html" . }}
|
||||
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
|
||||
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
|
||||
<div class="content">
|
||||
{{ if .Title }}<h1>{{ .Title }}</h1>{{ end }}
|
||||
{{ .Content }}
|
||||
</div>
|
||||
{{ partial "components/last-updated.html" . }}
|
||||
{{ partial "components/pager.html" . }}
|
||||
{{ partial "components/comments.html" . }}
|
||||
</main>
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
18
themes/hextra/layouts/docs/single.html
Normal file
18
themes/hextra/layouts/docs/single.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{ define "main" }}
|
||||
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
|
||||
{{ partial "sidebar.html" (dict "context" .) }}
|
||||
{{ partial "toc.html" . }}
|
||||
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
|
||||
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
|
||||
<div class="content">
|
||||
{{ if .Title }}<h1>{{ .Title }}</h1>{{ end }}
|
||||
{{ .Content }}
|
||||
</div>
|
||||
{{ partial "components/last-updated.html" . }}
|
||||
{{ partial "components/pager.html" . }}
|
||||
{{ partial "components/comments.html" . }}
|
||||
</main>
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
10
themes/hextra/layouts/hextra-home.html
Normal file
10
themes/hextra/layouts/hextra-home.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{ define "main" }}
|
||||
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
|
||||
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true) }}
|
||||
<div class="hx:w-full hx:break-words hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:pb-8 hx:pt-8 hx:md:pt-12 hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-left),1.5rem)]">
|
||||
<div class="hx:flex hx:flex-col hx:items-start">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
14
themes/hextra/layouts/home.html
Normal file
14
themes/hextra/layouts/home.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{{ define "main" }}
|
||||
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
|
||||
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
|
||||
{{ partial "toc.html" . }}
|
||||
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
|
||||
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
|
||||
<div class="content">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
</main>
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
18
themes/hextra/layouts/list.html
Normal file
18
themes/hextra/layouts/list.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{ define "main" }}
|
||||
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
|
||||
{{ partial "sidebar.html" (dict "context" .) }}
|
||||
{{ partial "toc.html" . }}
|
||||
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
|
||||
{{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
|
||||
<div class="content">
|
||||
{{ if .Title }}<h1>{{ .Title }}</h1>{{ end }}
|
||||
{{ .Content }}
|
||||
</div>
|
||||
<div class="hx:mt-16"></div>
|
||||
{{ partial "components/last-updated.html" . }}
|
||||
{{ partial "components/comments.html" . }}
|
||||
</main>
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
43
themes/hextra/layouts/list.rss.xml
Normal file
43
themes/hextra/layouts/list.rss.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{{ .Site.Title }} – {{ .Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
|
||||
<generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
|
||||
<language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
|
||||
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
|
||||
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
|
||||
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
|
||||
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
|
||||
{{ with .OutputFormats.Get "RSS" }}
|
||||
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
|
||||
{{ end }}
|
||||
{{ if not $.Section }}
|
||||
{{ $sections := .Site.Params.rss.sections | default (slice "blog") }}
|
||||
{{ .Store.Set "rssPages" (first 50 (where $.Site.RegularPages "Type" "in" $sections )) }}
|
||||
{{ else }}
|
||||
{{ if $.Parent.IsHome }}
|
||||
{{ .Store.Set "rssPages" (first 50 (where $.Site.RegularPages "Type" $.Section )) }}
|
||||
{{ else }}
|
||||
{{ .Store.Set "rssPages" (first 50 $.Pages) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range (.Store.Get "rssPages") }}
|
||||
<item>
|
||||
<title>{{ .Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
|
||||
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
|
||||
<guid>{{ .Permalink }}</guid>
|
||||
<description>
|
||||
{{ $img := (.Resources.ByType "image").GetMatch "*featured*" }}
|
||||
{{ with $img }}
|
||||
{{ $img := .Resize "640x" }}
|
||||
{{ printf "<![CDATA[<img src=\"%s\" width=\"%d\" height=\"%d\"/>]]>" $img.Permalink $img.Width $img.Height | safeHTML }}
|
||||
{{ end }}
|
||||
{{ .Content | html }}
|
||||
</description>
|
||||
</item>
|
||||
{{ end }}
|
||||
</channel>
|
||||
</rss>
|
||||
36
themes/hextra/layouts/llms.txt
Normal file
36
themes/hextra/layouts/llms.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
# {{ .Site.Title }}
|
||||
|
||||
> {{ .Site.Params.description }}
|
||||
|
||||
{{ range $section := site.Sections }}
|
||||
{{- template "llms-section-tree" dict "context" . "level" 2 }}
|
||||
{{ end }}
|
||||
|
||||
{{- $rootPages := where site.RegularPages "Section" "" }}
|
||||
{{- if $rootPages }}
|
||||
|
||||
## Root Pages
|
||||
{{- range $rootPages }}
|
||||
- [{{ .Title }}]({{ .Permalink }}): {{ .Summary | plainify | truncate 100 | strings.TrimSpace }}{{ if .Date }} - Published {{ .Date.Format "2006-01-02" }}{{ end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
---
|
||||
Generated on {{ now.Format "2006-01-02 15:04:05 UTC" }}
|
||||
Site: {{ .Site.BaseURL }}
|
||||
|
||||
{{- define "llms-section-tree" -}}
|
||||
{{- $context := .context -}}
|
||||
{{- $level := .level | default 2 -}}
|
||||
{{- $headerHashes := strings.Repeat $level "#" -}}
|
||||
{{- "\n" -}}
|
||||
{{ $headerHashes }} {{ $context.Title }}
|
||||
|
||||
{{- range $context.RegularPages }}
|
||||
- [{{ .Title }}]({{ .Permalink }}): {{ .Summary | plainify | truncate 100 | strings.TrimSpace }}{{ if .Date }} - Published {{ .Date.Format "2006-01-02" }}{{ end }}
|
||||
{{- end }}
|
||||
|
||||
{{- range $context.Sections }}
|
||||
{{ template "llms-section-tree" dict "context" . "level" (add $level 1) }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
19
themes/hextra/layouts/single.html
Normal file
19
themes/hextra/layouts/single.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{ define "main" }}
|
||||
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
|
||||
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
|
||||
{{ partial "toc.html" . }}
|
||||
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
|
||||
{{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
|
||||
<br class="hx:mt-1.5 hx:text-sm" />
|
||||
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
|
||||
<div class="hx:mb-16"></div>
|
||||
<div class="content">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
<div class="hx:mt-16"></div>
|
||||
{{ partial "components/comments.html" . }}
|
||||
</main>
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
30
themes/hextra/layouts/taxonomy.html
Normal file
30
themes/hextra/layouts/taxonomy.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{{ define "main" }}
|
||||
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
|
||||
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
|
||||
{{ partial "toc.html" (dict "Params" (dict "toc" false)) }}
|
||||
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
|
||||
<br class="hx:mt-1.5 hx:text-sm" />
|
||||
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
|
||||
<div class="hx:mb-16"></div>
|
||||
<div class="content">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
<div class="hx:grid hx:grid-cols-1 hx:md:grid-cols-2 hx:lg:grid-cols-3 hx:xl:grid-cols-4 hx:gap-4">
|
||||
{{ range .Data.Terms }}
|
||||
<div class="hx:w-full">
|
||||
<a
|
||||
href="{{ .Page.RelPermalink }}"
|
||||
title="{{ .Page.LinkTitle }}"
|
||||
class="hx:font-medium hx:hover:text-primary-600"
|
||||
>
|
||||
{{- .Page.LinkTitle -}}
|
||||
<span class="hx:text-sm hx:text-gray-500"> {{ .Count }}</span>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</main>
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user