Added a page header option to disable the table of contents, meant for shorter pages.

This commit is contained in:
Jeremy Karst 2025-06-19 01:04:55 -04:00
parent 2bd6274f92
commit bb1f3f9f1a
7 changed files with 356 additions and 2 deletions

View file

@ -5,6 +5,7 @@ description: "Projects, Guides, Thoughts and more."
cascade: cascade:
showEdit: false showEdit: false
showSummary: true showSummary: true
# disableToc: true # Uncomment this line to disable the table of contents
--- ---
{{< lead >}} {{< lead >}}

View file

@ -0,0 +1,20 @@
{{- $index := slice -}}
{{- range .Site.Pages -}}
{{- $section := .Site.GetPage "section" .Section -}}
{{- $showDate := .Params.showDate | default .Site.Params.article.showDate -}}
{{- $summary := .Summary | emojify -}}
{{- $summary := replaceRE "<h[1-6][^>]*>(.*?)</h[1-6]>" "$1" $summary -}}
{{- $summary := replaceRE "\\s*<br\\s*/?>" " " $summary -}}
{{- $summary := replaceRE "\\s+" " " $summary -}}
{{- $summary := trim $summary " " -}}
{{- $index = $index | append (dict
"date" (cond (and .IsPage $showDate) (.Date | time.Format (site.Params.dateFormat | default ":date_long")) nil)
"title" (.Title | emojify | safeJS)
"section" ($section.Title | emojify | safeJS)
"summary" ($summary | safeJS)
"content" (.Plain | emojify | safeJS)
"permalink" .RelPermalink
)
-}}
{{- end -}}
{{- $index | jsonify -}}

View file

@ -0,0 +1,82 @@
<article class="mt-6 flex max-w-prose flex-row">
{{- $images := $.Resources.ByType "image" }}
{{- $thumbnail := $images.GetMatch (.Params.thumbnail | default "*thumb*") }}
{{- $feature := $images.GetMatch (.Params.feature | default "*feature*") | default $thumbnail }}
{{- with $feature }}
<div class="flex-none pe-4 sm:pe-6 ">
<a
href="{{ with $.Params.externalUrl }}
{{ . }}
{{ else }}
{{ $.RelPermalink }}
{{ end }}"
aria-label="{{ $.Title | emojify }}"
>
<img
alt="{{ $.Params.featureAlt | default $.Params.thumbnailAlt | default "" }}"
{{ if eq .MediaType.SubType "svg" }}
class="w-24 max-w-[6rem] max-h-[4.5rem] rounded-md sm:max-h-[7.5rem] sm:w-40
sm:max-w-[10rem]" src="{{ .RelPermalink }}"
{{ else }}
class="w-24 rounded-md sm:w-40" srcset="
{{- (.Fill "160x120 smart").RelPermalink }}
160w, {{- (.Fill "320x240 smart").RelPermalink }} 2x"
src="{{ (.Fill "160x120 smart").RelPermalink }}" width="160" height="120"
{{ end }}
{{ if $.Site.Params.enableImageLazyLoading | default true }}
loading="lazy"
{{ end }}
/>
</a>
</div>
{{- end }}
<div>
<h3 class="flex items-center text-xl font-semibold">
{{ with .Params.externalUrl }}
<div>
<a
class="text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral"
href="{{ . }}"
target="_blank"
rel="external"
>{{ $.Title | emojify }}</a
>
<span
class="cursor-default align-top text-xs text-neutral-400 dark:text-neutral-500"
title="{{ i18n "list.externalurl_title" }}"
>
<span class="rtl:hidden">&#8599;</span>
<span class="ltr:hidden">&#8598;</span>
</span>
</div>
{{ else }}
<a
class="text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral"
href="{{ .RelPermalink }}"
>{{ .Title | emojify }}</a
>
{{ end }}
{{ if and .Draft .Site.Params.article.showDraftLabel }}
<div class="ms-2">
{{ partial "badge.html" (i18n "article.draft" | emojify) }}
</div>
{{ end }}
{{ if templates.Exists "partials/extend-article-link.html" }}
{{ partial "extend-article-link.html" . }}
{{ end }}
</h3>
<div class="text-sm text-neutral-500 dark:text-neutral-400">
{{ partial "article-meta.html" . }}
</div>
{{ if .Params.showSummary | default (.Site.Params.list.showSummary | default false) }}
<div class="prose py-1 dark:prose-invert">
{{ $summary := .Summary | emojify }}
{{ $summary := replaceRE "<h[1-6][^>]*>(.*?)</h[1-6]>" "$1" $summary }}
{{ $summary := replaceRE "\\s*<br\\s*/?>" " " $summary }}
{{ $summary := replaceRE "\\s+" " " $summary }}
{{ $summary := trim $summary " " }}
{{ $summary | safeHTML }}
</div>
{{ end }}
</div>
</article>

View file

@ -1,4 +1,4 @@
{{ if .TableOfContents }} {{ if and .TableOfContents (not (.Params.disableToc | default false)) }}
{{ $tocHighlight := resources.Get "js/toc-highlight.js" | js.Build | fingerprint }} {{ $tocHighlight := resources.Get "js/toc-highlight.js" | js.Build | fingerprint }}
<script src="{{ $tocHighlight.RelPermalink }}" integrity="{{ $tocHighlight.Data.Integrity }}" crossorigin="anonymous"></script> <script src="{{ $tocHighlight.RelPermalink }}" integrity="{{ $tocHighlight.Data.Integrity }}" crossorigin="anonymous"></script>
{{ end }} {{ end }}

154
layouts/partials/head.html Normal file
View file

@ -0,0 +1,154 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="{{ .Site.Params.defaultThemeColor | default "#ffffff" }}" />
{{/* Title */}}
{{ if .IsHome -}}
<title>{{ .Site.Title | emojify }}</title>
<meta name="title" content="{{ .Site.Title | emojify }}" />
{{- else -}}
<title>{{ .Title | emojify }} &middot; {{ .Site.Title | emojify }}</title>
<meta name="title" content="{{ .Title | emojify }} &middot; {{ .Site.Title | emojify }}" />
{{- end }}
{{/* Asset bundles */}}
{{ $assets := newScratch }}
{{ $algorithm := .Site.Params.fingerprintAlgorithm | default "sha256" }}
{{ $jsAppearance := resources.Get "js/appearance.js" }}
{{ $jsAppearance = $jsAppearance | resources.Minify | resources.Fingerprint $algorithm }}
<script
type="text/javascript"
src="{{ $jsAppearance.RelPermalink }}"
integrity="{{ $jsAppearance.Data.Integrity }}"
></script>
{{ $cssScheme := resources.Get (printf "css/schemes/%s.css" (lower .Site.Params.colorScheme | default "congo")) }}
{{ if not $cssScheme }}
{{ $cssScheme = resources.Get "css/schemes/congo.css" }}
{{ end }}
{{ $assets.Add "css" (slice $cssScheme) }}
{{ $cssMain := resources.Get "css/compiled/main.css" }}
{{ $assets.Add "css" (slice $cssMain) }}
{{ $cssCustom := resources.Get "css/custom.css" }}
{{ if $cssCustom }}
{{ $assets.Add "css" (slice $cssCustom) }}
{{ end }}
{{ $bundleCSS := $assets.Get "css" | resources.Concat "css/main.bundle.css" | resources.Minify | resources.Fingerprint $algorithm }}
<link
type="text/css"
rel="stylesheet"
href="{{ $bundleCSS.RelPermalink }}"
integrity="{{ $bundleCSS.Data.Integrity }}"
/>
{{ if .Site.Params.enableSearch | default false }}
{{ $jsFuse := resources.Get "lib/fuse/fuse.min.js" }}
{{ $jsSearch := resources.Get "js/search.js" }}
{{ $assets.Add "js" (slice $jsFuse $jsSearch) }}
{{ end }}
{{ if .Site.Params.enableQuicklink | default false }}
{{ $jsQuicklink := resources.Get "lib/quicklink/quicklink.umd.js" }}
{{ $jsQuicklinkInit := resources.Get "js/quicklink.js" }}
{{ $assets.Add "js" (slice $jsQuicklink $jsQuicklinkInit) }}
{{ end }}
{{ if .Site.Params.enableCodeCopy | default false }}
{{ $jsCode := resources.Get "js/code.js" }}
{{ $assets.Add "js" (slice $jsCode) }}
{{ end }}
{{ if or (eq .Site.Params.header.layout "hamburger") (eq .Site.Params.header.layout "hybrid") }}
{{ $jsMenu := resources.Get "js/menu.js" }}
{{ $assets.Add "js" (slice $jsMenu) }}
{{ end }}
{{ if eq (site.Language.LanguageDirection | default "ltr") "rtl" }}
{{ $jsRTL := resources.Get "js/rtl.js" }}
{{ $assets.Add "js" (slice $jsRTL) }}
{{ end }}
{{ if $assets.Get "js" }}
{{ $bundleJS := $assets.Get "js" | resources.Concat "js/main.bundle.js" | resources.Minify | resources.Fingerprint $algorithm }}
<script
defer
type="text/javascript"
id="script-bundle"
src="{{ $bundleJS.RelPermalink }}"
integrity="{{ $bundleJS.Data.Integrity }}"
data-copy="{{ i18n "code.copy" }}"
data-copied="{{ i18n "code.copied" }}"
></script>
{{ end }}
{{/* Metadata */}}
{{- $description := "" -}}
{{- with .Description -}}
{{- $description = . -}}
{{- else -}}
{{- if .IsPage -}}
{{- $summary := .Summary -}}
{{- $summary := replaceRE "<h[1-6][^>]*>(.*?)</h[1-6]>" "$1" $summary -}}
{{- $summary := replaceRE "\\s*<br\\s*/?>" " " $summary -}}
{{- $summary := replaceRE "\\s+" " " $summary -}}
{{- $description = trim $summary " " -}}
{{- else -}}
{{- with .Site.Params.description -}}
{{- $description = . -}}
{{- end -}}
{{- end -}}
{{- end -}}
<meta
name="description"
content="{{ $description }}"
/>
{{ with union .Site.Params.keywords .Params.keywords -}}
<meta name="keywords" content="{{ delimit . `, ` }}" />
{{- end }}
{{ with .Site.Params.robots }}
<meta name="robots" content="{{ . }}" />
{{ end }}
{{ with .Params.robots }}
<meta name="robots" content="{{ . }}" />
{{ end }}
<link rel="canonical" href="{{ .Permalink }}" />
{{ range .AlternativeOutputFormats -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .RelPermalink ($.Site.Title | emojify) | safeHTML }}
{{ end -}}
{{/* Icons */}}
{{ if templates.Exists "partials/favicons.html" }}
{{ partialCached "favicons.html" .Site }}
{{ else }}
<link rel="apple-touch-icon" sizes="180x180" href="{{ "apple-touch-icon.png" | relURL }}" />
<link rel="icon" type="image/png" sizes="32x32" href="{{ "favicon-32x32.png" | relURL }}" />
<link rel="icon" type="image/png" sizes="16x16" href="{{ "favicon-16x16.png" | relURL }}" />
<link rel="manifest" href="{{ "site.webmanifest" | relURL }}" />
{{ end }}
{{/* Site Verification */}}
{{ with .Site.Params.verification.google }}
<meta name="google-site-verification" content="{{ . }}" />
{{ end }}
{{ with .Site.Params.verification.bing }}
<meta name="msvalidate.01" content="{{ . }}" />
{{ end }}
{{ with .Site.Params.verification.pinterest }}
<meta name="p:domain_verify" content="{{ . }}" />
{{ end }}
{{ with .Site.Params.verification.yandex }}
<meta name="yandex-verification" content="{{ . }}" />
{{ end }}
{{ with $.Params.externalUrl }}
<meta http-equiv="refresh" content="0; url={{ . }}" />
{{ end }}
{{/* Social */}}
{{ template "_internal/opengraph.html" . }}
{{ template "_internal/twitter_cards.html" . }}
{{/* Schema */}}
{{ partial "schema.html" . }}
{{/* Me */}}
{{ with .Site.Language.Params.Author.name }}<meta name="author" content="{{ . }}" />{{ end }}
{{ with .Site.Language.Params.Author.links }}
{{ range $links := . }}
{{ range $name, $url := $links }}<link href="{{ $url }}" rel="me" />{{ end }}
{{ end }}
{{ end }}
{{/* Vendor */}}
{{ partial "vendor.html" . }}
{{/* Analytics */}}
{{ partial "analytics.html" . }}
{{/* Extend head - eg. for custom analytics scripts, etc. */}}
{{ if templates.Exists "partials/extend-head.html" }}
{{ partial "extend-head.html" . }}
{{ end }}
</head>

View file

@ -0,0 +1,97 @@
{{- define "partials/inline/breadcrumbData" -}}
{{- $scratchCtx := .scratch -}}
{{- with .parent }}
{{ partial "inline/breadcrumbData" (dict "scratch" $scratchCtx "parent" .Parent) }}
{{- $scratchCtx.Add "listItem" (slice (dict
"@type" "ListItem"
"position" ($scratchCtx.Get "count")
"name" (.Title | humanize | title)
"item" .Permalink
)) -}}
{{- $scratchCtx.Add "count" 1 -}}
{{- end }}
{{- end -}}
{{ if .IsHome -}}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"@id": "{{ (site.GetPage "/").Permalink | safeURL }}",
"name": "{{ .Site.Title | safeJS }}",
{{ with .Site.Params.description }}"description": "{{ . | safeJS }}",{{ end }}
{{ with .Site.LanguageCode }}"inLanguage": "{{ . }}",{{ end }}
"url": "{{ (site.GetPage "/").Permalink | safeURL }}",
{{ with .Site.Params.keywords }}"keywords": {{ . }},{{ end }}
"publisher" : {
"@type": "Person",
"name": "{{ .Site.Language.Params.Author.name | safeJS }}"
}
}
</script>
{{ else if .IsPage }}
{{- $iso8601 := "2006-01-02T15:04:05-07:00" -}}
{{- $summary := .Summary -}}
{{- $summary := replaceRE "<h[1-6][^>]*>(.*?)</h[1-6]>" "$1" $summary -}}
{{- $summary := replaceRE "\\s*<br\\s*/?>" " " $summary -}}
{{- $summary := replaceRE "\\s+" " " $summary -}}
{{- $summary := trim $summary " " -}}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"articleSection": "{{ (site.GetPage .Section).Title | safeJS }}",
"name": "{{ .Title | safeJS }}",
"headline": "{{ .Title | safeJS }}",
{{ with .Description }}"description": "{{ . | safeJS }}",{{ end }}
{{ with $summary }}"abstract": "{{ . | safeJS }}",{{ end }}
{{ with .Site.LanguageCode }}"inLanguage": "{{ . }}",{{ end }}
"url" : "{{ .Permalink }}",
"author" : {
"@type": "Person",
"name": "{{ .Site.Language.Params.Author.name | safeJS }}"
},
{{ with .PublishDate }}"copyrightYear": "{{ .Format "2006" }}",{{ end }}
{{ with .Date }}"dateCreated": "{{ .Format $iso8601 }}",{{ end }}
{{ with .PublishDate }}"datePublished": "{{ .Format $iso8601 }}",{{ end }}
{{ with .ExpiryDate }}"expires": "{{ .Format $iso8601 }}",{{ end }}
{{ with .Lastmod }}"dateModified": "{{ .Format $iso8601 }}",{{ end }}
{{ if .Keywords }}
{{ with .Keywords }}"keywords": {{ . }},{{ end }}
{{ else }}
{{ with .Params.tags }}"keywords": {{ . }},{{ end }}
{{ end }}
"mainEntityOfPage": "true",
"wordCount": "{{ .WordCount }}"
}
</script>
{{- /* Source: https://bullaki.com/projects/web-design/seo-with-hugo-5-breadcrumbs/ */}}
{{- $scratch := newScratch -}}
{{- $scratch.Set "count" 1 -}}
{{ partial "inline/breadcrumbData" (dict "scratch" $scratch "parent" .Parent) }}
{{- with index (.GetTerms "categories") 0 -}}
{{- $scratch.Add "listItem" (slice (dict
"@type" "ListItem"
"position" ($scratch.Get "count")
"name" .LinkTitle
"item" .Permalink
)) -}}
{{- $scratch.Add "count" 1 -}}
{{- end }}
{{- $scratch.Add "listItem" (slice (dict
"@type" "ListItem"
"position" ($scratch.Get "count")
"name" (.Title | humanize | title)
)) -}}
{{- $scratch.SetInMap "breadcrumb" "@context" "https://schema.org" -}}
{{- $scratch.SetInMap "breadcrumb" "@type" "BreadcrumbList" -}}
{{- $scratch.SetInMap "breadcrumb" "itemListElement" ($scratch.Get "listItem") -}}
<script type="application/ld+json">
{{ $scratch.Get "breadcrumb" | jsonify (dict "prefix" " " "indent" " ") | safeJS }}
</script>
{{- end }}

View file

@ -35,7 +35,7 @@
{{ end }} {{ end }}
</header> </header>
<section class="prose mt-0 flex max-w-full flex-col dark:prose-invert lg:flex-row"> <section class="prose mt-0 flex max-w-full flex-col dark:prose-invert lg:flex-row">
{{ if and (.Params.showTableOfContents | default (.Site.Params.article.showTableOfContents | default false)) (in .TableOfContents "<ul") }} {{ if and (not (.Params.disableToc | default false)) (.Params.showTableOfContents | default (.Site.Params.article.showTableOfContents | default false)) (in .TableOfContents "<ul") }}
<div class="order-first px-0 lg:order-last lg:max-w-xs lg:ps-8"> <div class="order-first px-0 lg:order-last lg:max-w-xs lg:ps-8">
<div class="toc pe-5 lg:sticky lg:top-10 print:hidden"> <div class="toc pe-5 lg:sticky lg:top-10 print:hidden">
{{ partial "toc.html" . }} {{ partial "toc.html" . }}