mirror of
https://github.com/imfing/hextra.git
synced 2025-08-24 21:46:39 -04:00
chore: adapt theme to new template system (#696)
* Adapted theme's layout to [Hugo v0.146.0](https://gohugo.io/templates/new-templatesystem-overview/) * Bumped minimal Hugo version to v0.146.0
This commit is contained in:
15
layouts/_partials/breadcrumb.html
Normal file
15
layouts/_partials/breadcrumb.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{{- if (default true .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 .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" . -}}
|
||||
</div>
|
||||
</div>
|
||||
{{ end -}}
|
15
layouts/_partials/components/codeblock-copy-button.html
Normal file
15
layouts/_partials/components/codeblock-copy-button.html
Normal file
@@ -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="copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
|
||||
<div class="success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
|
||||
</button>
|
||||
</div>
|
29
layouts/_partials/components/codeblock.html
Normal file
29
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="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
layouts/_partials/components/comments.html
Normal file
11
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 -}}
|
71
layouts/_partials/components/giscus.html
Normal file
71
layouts/_partials/components/giscus.html
Normal file
@@ -0,0 +1,71 @@
|
||||
{{- $lang := site.Language.LanguageCode | default `en` -}}
|
||||
|
||||
{{- with site.Params.comments.giscus -}}
|
||||
<script>
|
||||
/*
|
||||
* "preferred color scheme" theme in giscus works using "prefers-color-scheme" in media query.
|
||||
* but, hugo's theme switch function works by using "color-theme" in local storage.
|
||||
* This solution was created with reference to:
|
||||
* https://github.com/giscus/giscus/issues/336#issuecomment-1214366281
|
||||
*/
|
||||
function getHugoTheme() {
|
||||
return localStorage.getItem("color-theme");
|
||||
}
|
||||
|
||||
function getGiscusTheme() {
|
||||
let giscusTheme = "{{ (string .theme) | default `light` }}";
|
||||
if(getHugoTheme() == 'light') {
|
||||
return giscusTheme.replace('dark', 'light');
|
||||
} else {
|
||||
return giscusTheme.replace('light', 'dark');
|
||||
}
|
||||
}
|
||||
|
||||
function setGiscusTheme() {
|
||||
function sendMessage(message) {
|
||||
const iframe = document.querySelector('iframe.giscus-frame');
|
||||
if (!iframe) return;
|
||||
iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app');
|
||||
}
|
||||
sendMessage({
|
||||
setConfig: {
|
||||
theme: getGiscusTheme(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
document.getElementById('giscus').appendChild(giscusScript);
|
||||
|
||||
// Update giscus theme when theme switcher is clicked
|
||||
const toggles = document.querySelectorAll(".theme-toggle");
|
||||
if (toggles) {
|
||||
toggles.forEach(toggle => toggle.addEventListener('click', setGiscusTheme));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="giscus"></div>
|
||||
{{- else -}}
|
||||
{{ warnf "giscus is not configured" }}
|
||||
{{- end -}}
|
42
layouts/_partials/components/github-style-alert.html
Normal file
42
layouts/_partials/components/github-style-alert.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{{- $content := .content -}}
|
||||
{{- $alertType := .alertType -}}
|
||||
{{- $alertTitle := .alertTitle -}}
|
||||
|
||||
{{- $icons := dict
|
||||
"note" "information-circle"
|
||||
"tip" "light-bulb"
|
||||
"important" "information-circle"
|
||||
"warning" "exclamation"
|
||||
"caution" "exclamation-circle"
|
||||
-}}
|
||||
|
||||
{{- $icon := index $icons $alertType -}}
|
||||
{{- $title := or $alertTitle (or (i18n $alertType) (title $alertType)) -}}
|
||||
|
||||
{{- $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" }}
|
||||
|
||||
{{- $alertClasses := dict
|
||||
"note" "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"
|
||||
"tip" "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"
|
||||
"important" "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"
|
||||
"warning" "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"
|
||||
"caution" "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"
|
||||
-}}
|
||||
|
||||
{{- $class := index $alertClasses $alertType | default $defaultClass -}}
|
||||
|
||||
|
||||
<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 {{ $class }}">
|
||||
<p class="hx:flex hx:items-center hx:font-medium">
|
||||
{{- with $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
layouts/_partials/components/last-updated.html
Normal file
12
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
layouts/_partials/components/pager.html
Normal file
53
layouts/_partials/components/pager.html
Normal file
@@ -0,0 +1,53 @@
|
||||
{{/* Article navigation on the footer of the article */}}
|
||||
|
||||
{{- $reversePagination := .Scratch.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
layouts/_partials/custom/footer.html
Normal file
0
layouts/_partials/custom/footer.html
Normal file
0
layouts/_partials/custom/head-end.html
Normal file
0
layouts/_partials/custom/head-end.html
Normal file
7
layouts/_partials/favicons.html
Normal file
7
layouts/_partials/favicons.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<link rel="icon shortcut" href="{{ "favicon.ico" | relURL }}" sizes="32x32" />
|
||||
<link rel="icon" href="{{ "favicon.svg" | relURL }}" type="image/svg+xml" />
|
||||
<link rel="icon" href="{{ "favicon-dark.svg" | relURL }}" type="image/svg+xml" media="(prefers-color-scheme: dark)" />
|
||||
<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
layouts/_partials/footer.html
Normal file
44
layouts/_partials/footer.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{{- $enableFooterSwitches := .Scratch.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 -}}
|
13
layouts/_partials/google-analytics.html
Normal file
13
layouts/_partials/google-analytics.html
Normal file
@@ -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 -}}
|
108
layouts/_partials/head.html
Normal file
108
layouts/_partials/head.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<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 }}
|
||||
|
||||
|
||||
<!-- Google Analytics -->
|
||||
{{- if and hugo.IsProduction .Site.Config.Services.GoogleAnalytics.ID }}
|
||||
<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin />
|
||||
{{ partial "google-analytics.html" . -}}
|
||||
{{- end }}
|
||||
|
||||
<script>
|
||||
/* Initialize light/dark mode */
|
||||
const defaultTheme = '{{ site.Params.theme.default | default `system`}}';
|
||||
|
||||
const setDarkTheme = () => {
|
||||
document.documentElement.classList.add("dark");
|
||||
document.documentElement.style.colorScheme = "dark";
|
||||
}
|
||||
const setLightTheme = () => {
|
||||
document.documentElement.classList.remove("dark");
|
||||
document.documentElement.style.colorScheme = "light";
|
||||
}
|
||||
|
||||
if ("color-theme" in localStorage) {
|
||||
localStorage.getItem("color-theme") === "dark" ? setDarkTheme() : setLightTheme();
|
||||
} else {
|
||||
defaultTheme === "dark" ? setDarkTheme() : setLightTheme();
|
||||
if (defaultTheme === "system") {
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches ? setDarkTheme() : setLightTheme();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- KaTeX-->
|
||||
{{ $noop := .WordCount -}}
|
||||
{{ if .Page.Store.Get "hasMath" -}}
|
||||
<!-- TODO: make url configurable -->
|
||||
{{ $katexBaseUrl := "https://cdn.jsdelivr.net/npm/katex@latest/dist" }}
|
||||
{{ $katexCssUrl := printf "%s/katex%s.css" $katexBaseUrl (cond hugo.IsProduction ".min" "") -}}
|
||||
{{ $katexFontPattern := "url(fonts/" }}
|
||||
{{ $katexFontSubstituted := printf "url(%s/fonts/" $katexBaseUrl }}
|
||||
|
||||
{{ with try (resources.GetRemote $katexCssUrl) -}}
|
||||
{{ with .Err -}}
|
||||
{{ errorf "Could not retrieve KaTeX css file from %s. Reason: %s." $katexCssUrl . -}}
|
||||
{{ else with.Value -}}
|
||||
{{ $katexCssContent := strings.Replace .Content $katexFontPattern $katexFontSubstituted }}
|
||||
{{ with resources.FromString (printf "css/katex%s.css" (cond hugo.IsProduction ".min" "")) $katexCssContent -}}
|
||||
<link rel="stylesheet" href="{{- .RelPermalink -}}" integrity="{{- . | fingerprint -}}" crossorigin="anonymous" />
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ partial "custom/head-end.html" . -}}
|
||||
</head>
|
44
layouts/_partials/language-switch.html
Normal file
44
layouts/_partials/language-switch.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{{- $page := .context -}}
|
||||
|
||||
{{- $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"
|
||||
class="language-switcher hx:cursor-pointer hx:h-7 hx:rounded-md hx:px-2 hx:text-left hx:text-xs hx:font-medium 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 hx:grow"
|
||||
type="button"
|
||||
aria-label="{{ $changeLanguage }}"
|
||||
>
|
||||
<div class="hx:flex hx:items-center hx:gap-2 hx:capitalize">
|
||||
{{- partial "utils/icon" (dict "name" "globe-alt" "attributes" "height=12") -}}
|
||||
{{- if not $hideLabel }}<span>{{ site.Language.LanguageName }}</span>{{ end -}}
|
||||
</div>
|
||||
</button>
|
||||
<ul
|
||||
class="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 -}}
|
71
layouts/_partials/navbar.html
Normal file
71
layouts/_partials/navbar.html
Normal file
@@ -0,0 +1,71 @@
|
||||
{{- $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 -}}
|
||||
|
||||
{{- $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 -}}
|
||||
|
||||
<div class="nav-container hx:sticky hx:top-0 hx:z-20 hx:w-full hx:bg-transparent hx:print:hidden">
|
||||
<div class="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">
|
||||
<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>
|
||||
|
||||
{{- $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 -}}
|
||||
|
||||
{{/* Display icon menu item */}}
|
||||
{{- if .Params.icon -}}
|
||||
{{- $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" "height=24") -}}
|
||||
<span class="hx:sr-only">{{ or (T .Identifier) .Name | safeHTML }}</span>
|
||||
</a>
|
||||
{{- else -}}
|
||||
{{- $active := or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .) -}}
|
||||
{{- $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" -}}
|
||||
<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-block {{ $activeClass }}"
|
||||
>
|
||||
<span class="hx:text-center">{{ or (T .Identifier) .Name | safeHTML }}</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<button type="button" aria-label="Menu" class="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" "height=24") -}}
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
31
layouts/_partials/opengraph.html
Normal file
31
layouts/_partials/opengraph.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{{/* From https://github.com/gohugoio/gohugoioTheme/blob/master/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 . }}<meta property="og:image" content="{{ . | absURL }}" />{{ end -}}
|
||||
{{- else -}}
|
||||
{{- $featured := "" }}
|
||||
{{- with $featured -}}
|
||||
<meta property="og:image" content="{{ $featured.Permalink }}"/>
|
||||
{{- else -}}
|
||||
{{- with $.Site.Params.images }}<meta property="og:image" content="{{ index . 0 | absURL }}"/>{{ end -}}
|
||||
{{- 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 }}
|
10
layouts/_partials/scripts.html
Normal file
10
layouts/_partials/scripts.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{/* 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 -}}
|
14
layouts/_partials/scripts/core.html
Normal file
14
layouts/_partials/scripts/core.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{{- $jsTheme := resources.Get "js/theme.js" | resources.ExecuteAsTemplate "theme.js" . -}}
|
||||
{{- $jsMenu := resources.Get "js/menu.js" -}}
|
||||
{{- $jsTabs := resources.Get "js/tabs.js" -}}
|
||||
{{- $jsLang := resources.Get "js/lang.js" -}}
|
||||
{{- $jsCodeCopy := resources.Get "js/code-copy.js" -}}
|
||||
{{- $jsFileTree := resources.Get "js/filetree.js" -}}
|
||||
{{- $jsSidebar := resources.Get "js/sidebar.js" -}}
|
||||
{{- $jsBackToTop := resources.Get "js/back-to-top.js" -}}
|
||||
|
||||
{{- $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang $jsFileTree $jsSidebar $jsBackToTop | resources.Concat "js/main.js" -}}
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- $scripts = $scripts | minify | fingerprint -}}
|
||||
{{- end -}}
|
||||
<script defer src="{{ $scripts.RelPermalink }}" integrity="{{ $scripts.Data.Integrity }}"></script>
|
44
layouts/_partials/scripts/mermaid.html
Normal file
44
layouts/_partials/scripts/mermaid.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{{/* Mermaid */}}
|
||||
|
||||
{{ $mermaidJsUrl := printf "https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid%s.js" (cond hugo.IsProduction ".min" "") -}}
|
||||
{{ 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.min.js") . -}}
|
||||
{{ $mermaidJs := . | fingerprint -}}
|
||||
<script defer src="{{ $mermaidJs.RelPermalink }}" integrity="{{ $mermaidJs.Data.Integrity }}" crossorigin="anonymous"></script>
|
||||
{{ end -}}
|
||||
{{ 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
layouts/_partials/scripts/search.html
Normal file
26
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
layouts/_partials/search.html
Normal file
26
layouts/_partials/search.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{{- $placeholder := (T "searchPlaceholder") | default "Search..." -}}
|
||||
|
||||
|
||||
<div class="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="search-input 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="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>
|
20
layouts/_partials/shortcodes/badge.html
Normal file
20
layouts/_partials/shortcodes/badge.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{- $content := .content -}}
|
||||
{{- $type := .type -}}
|
||||
{{- $class := .class | default "" -}}
|
||||
{{- $border := .border | default false -}}
|
||||
{{- $icon := .icon | default "" -}}
|
||||
|
||||
{{- $defaultClass := "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" -}}
|
||||
{{- $warningClass := "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" -}}
|
||||
{{- $infoClass := "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" -}}
|
||||
{{- $errorClass := "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 := cond (eq $type "info") $infoClass (cond (eq $type "warning") $warningClass (cond (eq $type "error") $errorClass $defaultClass)) -}}
|
||||
<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
layouts/_partials/shortcodes/callout.html
Normal file
28
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>
|
66
layouts/_partials/shortcodes/card.html
Normal file
66
layouts/_partials/shortcodes/card.html
Normal file
@@ -0,0 +1,66 @@
|
||||
{{- $page := .page -}}
|
||||
{{- $link := .link -}}
|
||||
{{- $title := .title -}}
|
||||
{{- $icon := .icon -}}
|
||||
{{- $subtitle := .subtitle -}}
|
||||
{{- $image := .image -}}
|
||||
{{- $width := .width -}}
|
||||
{{- $height := .height -}}
|
||||
{{- $imageStyle := .imageStyle -}}
|
||||
{{- $tag := .tag -}}
|
||||
{{- $tagType := .tagType -}}
|
||||
|
||||
{{ $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="{{ $title }}"
|
||||
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
|
||||
"type" $tagType
|
||||
"class" "hextra-card-tag"
|
||||
"border" true
|
||||
)
|
||||
-}}
|
||||
{{- end -}}
|
||||
</a>
|
||||
{{- /* Strip trailing newline. */ -}}
|
6
layouts/_partials/shortcodes/cards.html
Normal file
6
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>
|
183
layouts/_partials/sidebar.html
Normal file
183
layouts/_partials/sidebar.html
Normal file
@@ -0,0 +1,183 @@
|
||||
{{- $context := .context -}}
|
||||
|
||||
{{- $disableSidebar := .disableSidebar | default false -}}
|
||||
{{- $displayPlaceholder := .displayPlaceholder | default false -}}
|
||||
|
||||
{{- $sidebarClass := cond $disableSidebar (cond $displayPlaceholder "hx:md:hidden hx:xl:block" "hx:md:hidden") "hx:md:sticky" -}}
|
||||
|
||||
{{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}}
|
||||
{{- $pageURL := $context.RelPermalink -}}
|
||||
|
||||
{{/* EXPERIMENTAL */}}
|
||||
{{- if .context.Params.sidebar.hide -}}
|
||||
{{- $disableSidebar = true -}}
|
||||
{{- $displayPlaceholder = true -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<aside class="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.Scratch.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) }}{{ 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 }}
|
||||
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 -}}
|
21
layouts/_partials/theme-toggle.html
Normal file
21
layouts/_partials/theme-toggle.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- $hideLabel := .hideLabel | default false -}}
|
||||
|
||||
{{- $changeTheme := (T "changeTheme") | default "Change theme" -}}
|
||||
{{- $light := (T "light") | default "Light" -}}
|
||||
{{- $dark := (T "dark") | default "Dark" -}}
|
||||
|
||||
|
||||
<button
|
||||
title="{{ $changeTheme }}"
|
||||
data-theme="light"
|
||||
class="theme-toggle hx:cursor-pointer hx:group hx:h-7 hx:rounded-md hx:px-2 hx:text-left hx:text-xs hx:font-medium 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"
|
||||
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" "height=12 class=\"hx:group-data-[theme=light]:hidden\"") -}}
|
||||
{{- if not $hideLabel }}<span class="hx:group-data-[theme=light]:hidden">{{ $light }}</span>{{ end -}}
|
||||
{{- partial "utils/icon.html" (dict "name" "moon" "attributes" "height=12 class=\"hx:group-data-[theme=dark]:hidden\"") -}}
|
||||
{{- if not $hideLabel }}<span class="hx:group-data-[theme=dark]:hidden">{{ $dark }}</span>{{ end -}}
|
||||
</div>
|
||||
</button>
|
81
layouts/_partials/toc.html
Normal file
81
layouts/_partials/toc.html
Normal file
@@ -0,0 +1,81 @@
|
||||
{{/* Table of Contents */}}
|
||||
{{/* TODO: toc bottom part should be able to hide */}}
|
||||
{{- $toc := .Params.toc | default true -}}
|
||||
{{- $onThisPage := (T "onThisPage") | default "On this page"}}
|
||||
{{- $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 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-semibold" (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 -}}
|
21
layouts/_partials/utils/file-path.html
Normal file
21
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
layouts/_partials/utils/format-date.html
Normal file
3
layouts/_partials/utils/format-date.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{{- with . -}}
|
||||
{{- . | time.Format (site.Params.dateFormat | default ":date_long") -}}
|
||||
{{- end -}}
|
62
layouts/_partials/utils/fragments.html
Normal file
62
layouts/_partials/utils/fragments.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{{/* Split page raw content into fragments */}}
|
||||
{{ $page := .context }}
|
||||
{{ $type := .type | default "content" }}
|
||||
|
||||
{{ $headingKeys := slice }}
|
||||
{{ $headingTitles := slice }}
|
||||
|
||||
{{ range $h1 := $page.Fragments.Headings }}
|
||||
{{ if eq $h1.Title "" }}
|
||||
{{ $headingKeys = $headingKeys | append $h1.Title }}
|
||||
{{ else }}
|
||||
{{ $headingKeys = $headingKeys | append (printf "%s#%s" $h1.ID $h1.Title) }}
|
||||
{{ end }}
|
||||
{{ $headingTitles = $headingTitles | append (printf "# %s" $h1.Title) }}
|
||||
|
||||
{{ range $h2 := $h1.Headings }}
|
||||
{{ $headingKeys = $headingKeys | append (printf "%s#%s" $h2.ID $h2.Title) }}
|
||||
{{ $headingTitles = $headingTitles | append (printf "## %s" $h2.Title) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ $content := $page.RawContent }}
|
||||
{{ $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 | chomp)) }}
|
||||
{{ 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 | $page.RenderString | plainify | htmlUnescape | chomp)) }}
|
||||
{{ else }}
|
||||
{{ $parts := split $content (printf "\n%s\n" $headingTitle) }}
|
||||
{{ $lastPart := index $parts (sub (len $parts) 1) }}
|
||||
|
||||
{{ $data = $data | merge (dict $headingKey ($lastPart | $page.RenderString | plainify | htmlUnescape | chomp)) }}
|
||||
{{ $content = strings.TrimSuffix $lastPart $content }}
|
||||
{{ $content = strings.TrimSuffix (printf "\n%s\n" $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 | chomp)) }}
|
||||
{{ end }}
|
||||
|
||||
{{ return $data }}
|
14
layouts/_partials/utils/icon.html
Normal file
14
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
layouts/_partials/utils/lang-link.html
Normal file
25
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
layouts/_partials/utils/page-description.html
Normal file
11
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 -}}
|
32
layouts/_partials/utils/sort-pages.html
Normal file
32
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
layouts/_partials/utils/title.html
Normal file
19
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 -}}
|
Reference in New Issue
Block a user