Skip to content

Commit 30866e3

Browse files
authored
chore: enhance tags appearance in toc and minor updates (#739)
* chore: remove standard development mode command from CLAUDE.md * docs(toc): add TOC scroll functionality documentation - Added detailed comments to toc-scroll.js explaining the purpose and functionality of the TOC scroll spy feature. - Clarified the requirements for proper operation, including the need for a .hextra-toc element and matching heading IDs. * refactor(fragments): enhance fragment processing and documentation - Improved the fragments.html partial to better handle page content splitting into searchable fragments based on headings. - Added comprehensive documentation within the file, detailing parameters, return values, and examples for clarity. - Updated content handling to ensure whitespace is trimmed * refactor(tags): enhance tag rendering and styling in toc - Updated the tags.html partial to utilize a context variable for improved accessibility. - Enhanced the styling of tag links for better visual consistency across different themes. - Modified toc.html and list.html to pass the context to the tags partial, ensuring consistent rendering. * chore: run `task css`
1 parent 0bb59d6 commit 30866e3

8 files changed

Lines changed: 97 additions & 46 deletions

File tree

CLAUDE.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ Hextra is a modern, responsive Hugo theme designed for creating documentation we
1414
# Start development server with theme reloading (recommended for theme development)
1515
npm run dev:theme
1616

17-
# Standard development mode
18-
npm run dev
19-
2017
# Using Task runner
2118
task dev
2219
```

assets/css/compiled/main.css

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/js/toc-scroll.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
// TOC Scroll Highlight
1+
/**
2+
* TOC Scroll - Highlights active TOC links based on visible headings
3+
*
4+
* Uses Intersection Observer to track heading visibility and applies
5+
* 'hextra-toc-active' class to corresponding TOC links. Selects the
6+
* topmost heading when multiple are visible.
7+
*
8+
* Requires: .hextra-toc element, matching heading IDs, toc.css styles
9+
*/
210
document.addEventListener("DOMContentLoaded", function () {
311
const toc = document.querySelector(".hextra-toc");
412
if (!toc) return;

exampleSite/hugo_stats.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@
334334
"hx:gap-2",
335335
"hx:gap-4",
336336
"hx:gap-x-1.5",
337+
"hx:gap-x-2",
338+
"hx:gap-y-1",
337339
"hx:gap-y-2",
338340
"hx:grid",
339341
"hx:grid-cols-1",
@@ -440,7 +442,6 @@
440442
"hx:max-w-[min(calc(100vw-2rem),calc(100%+20rem))]",
441443
"hx:max-w-none",
442444
"hx:max-xl:hidden",
443-
"hx:mb-1",
444445
"hx:mb-10",
445446
"hx:mb-12",
446447
"hx:mb-16",

layouts/_partials/tags.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
<p class="hx:opacity-50 hx:text-sm hx:leading-7">
2-
{{- range $tag := .Params.tags -}}
3-
{{- with $.Site.GetPage (printf "/tags/%s" $tag) -}}
4-
<a class="hx:inline-block hx:mr-2" href="{{ .RelPermalink }}">#{{ .Title }}</a>
5-
{{- end -}}
1+
{{- $context := .context -}}
2+
3+
{{- range $tag := $context.Params.tags -}}
4+
{{- with $context.Site.GetPage (printf "/tags/%s" $tag) -}}
5+
<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 }}">#{{ .Title }}</a>
66
{{- end -}}
7-
</p>
7+
{{- end -}}

layouts/_partials/toc.html

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,20 @@
2020

2121
{{- $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]" -}}
2222

23-
{{- if and site.Params.toc.displayTags .Params.tags -}}
24-
<div class="{{ $borderClass }} hx:sticky hx:bottom-0 hx:flex hx:flex-col hx:items-start hx:gap-2 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">
25-
<p class="hx:mb-1 hx:font-semibold hx:tracking-tight">{{ $tags }}</p>
26-
{{ partial "tags.html" . }}
27-
</div>
28-
{{- end -}}
29-
3023
{{- if not .Fragments.Headings -}}
3124
{{- $borderClass = "" -}}
3225
{{- end -}}
3326

3427
{{/* TOC bottom part */}}
3528
<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">
29+
{{- if and site.Params.toc.displayTags .Params.tags -}}
30+
<div class="hx:flex hx:items-start hx:gap-x-2 hx:font-medium hx:text-xs">
31+
<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>
32+
<div class="hx:flex hx:flex-wrap hx:gap-y-1">
33+
{{ partial "tags.html" (dict "context" .) }}
34+
</div>
35+
</div>
36+
{{- end -}}
3637
{{- if site.Params.editURL.enable -}}
3738
{{- $editURL := site.Params.editURL.base | default "" -}}
3839
{{- with .Params.editURL -}}
Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,74 @@
1-
{{/* Split page raw content into fragments */}}
2-
{{ $page := .context }}
3-
{{ $type := .type | default "content" }}
1+
{{- /*
2+
fragments.html - Split page content into searchable fragments
3+
4+
This partial processes a Hugo page and splits its content into fragments based on headings,
5+
creating a data structure suitable for search indexing. It supports different fragment types
6+
and handles hierarchical heading structures (h1, h2).
7+
8+
Parameters:
9+
- .context (Page): The Hugo page to process
10+
- .type (string): Fragment type - "content" (default), "heading", "title", or "summary"
11+
12+
Returns:
13+
- dict: Map of heading keys to content fragments
14+
15+
Example:
16+
Input page with content:
17+
# Introduction
18+
This is the intro text.
19+
## Setup
20+
Setup instructions here.
21+
# Configuration
22+
Config details here.
23+
24+
Output (type "content"):
25+
{
26+
"": "This is the intro text.",
27+
"intro#Introduction": "This is the intro text. Setup instructions here.",
28+
"setup#Setup": "Setup instructions here.",
29+
"config#Configuration": "Config details here."
30+
}
31+
32+
Fragment types:
33+
- "content": Splits page content by headings (default)
34+
- "heading": Returns heading keys with empty content
35+
- "title": Returns empty content (title handled elsewhere)
36+
- "summary": Returns page summary only
37+
*/ -}}
438

5-
{{ $headingKeys := slice }}
6-
{{ $headingTitles := slice }}
39+
{{- /* Extract page context and fragment type */ -}}
40+
{{- $page := .context -}}
41+
{{- $type := .type | default "content" -}}
742

8-
{{ range $h1 := $page.Fragments.Headings }}
9-
{{ if eq $h1.Title "" }}
10-
{{ $headingKeys = $headingKeys | append $h1.Title }}
11-
{{ else }}
12-
{{ $headingKeys = $headingKeys | append (printf "%s#%s" $h1.ID $h1.Title) }}
13-
{{ end }}
14-
{{ $headingTitles = $headingTitles | append (printf "<h1>%s" $h1.Title) }}
43+
{{- /* Initialize slices to store heading data */ -}}
44+
{{- $headingKeys := slice -}} {{- /* Keys for indexing (ID#Title or just Title) */ -}}
45+
{{- $headingTitles := slice -}} {{- /* HTML heading tags for content splitting */ -}}
1546

16-
{{ range $h2 := $h1.Headings }}
17-
{{ $headingKeys = $headingKeys | append (printf "%s#%s" $h2.ID $h2.Title) }}
18-
{{ $headingTitles = $headingTitles | append (printf "<h2>%s" $h2.Title) }}
19-
{{ end }}
20-
{{ end }}
47+
{{- /* Process all h1 headings and their nested h2 headings */ -}}
48+
{{- range $h1 := $page.Fragments.Headings -}}
49+
{{- /* Handle h1 headings - empty titles get special treatment */ -}}
50+
{{- if eq $h1.Title "" -}}
51+
{{- $headingKeys = $headingKeys | append $h1.Title -}}
52+
{{- else -}}
53+
{{- $headingKeys = $headingKeys | append (printf "%s#%s" $h1.ID $h1.Title) -}}
54+
{{- end -}}
55+
{{- $headingTitles = $headingTitles | append (printf "<h1>%s" $h1.Title) -}}
56+
57+
{{- /* Process nested h2 headings */ -}}
58+
{{- range $h2 := $h1.Headings -}}
59+
{{- $headingKeys = $headingKeys | append (printf "%s#%s" $h2.ID $h2.Title) -}}
60+
{{- $headingTitles = $headingTitles | append (printf "<h2>%s" $h2.Title) -}}
61+
{{- end -}}
62+
{{- end -}}
2163

22-
{{ $content := $page.Content | htmlUnescape }}
23-
{{ $len := len $headingKeys }}
24-
{{ $data := dict }}
64+
{{- $content := $page.Content | htmlUnescape -}}
65+
{{- $len := len $headingKeys -}}
66+
{{- $data := dict -}}
2567

2668
{{ if eq $type "content" }}
2769
{{/* Include full content of the page */}}
2870
{{ if eq $len 0 }}
29-
{{ $data = $data | merge (dict "" ($page.Plain | htmlUnescape | chomp)) }}
71+
{{ $data = $data | merge (dict "" ($page.Plain | htmlUnescape | strings.TrimSpace)) }}
3072
{{ else }}
3173
{{/* Split the raw content from bottom to top */}}
3274
{{ range seq $len }}
@@ -35,12 +77,12 @@
3577
{{ $headingTitle := index $headingTitles $i }}
3678

3779
{{ if eq $i 0 }}
38-
{{ $data = $data | merge (dict $headingKey ($content | plainify | htmlUnescape | chomp)) }}
80+
{{ $data = $data | merge (dict $headingKey ($content | plainify | htmlUnescape | strings.TrimSpace)) }}
3981
{{ else }}
4082
{{ $parts := split $content (printf "%s" $headingTitle) }}
41-
{{ $lastPart := index $parts (sub (len $parts) 1) }}
83+
{{ $lastPart := index $parts (sub (len $parts) 1) | strings.TrimSpace }}
4284

43-
{{ $data = $data | merge (dict $headingKey ($lastPart | plainify | htmlUnescape | chomp)) }}
85+
{{ $data = $data | merge (dict $headingKey ($lastPart | plainify | htmlUnescape | strings.TrimSpace)) }}
4486
{{ $content = strings.TrimSuffix $lastPart $content }}
4587
{{ $content = strings.TrimSuffix (printf "%s" $headingTitle) $content }}
4688
{{ end }}
@@ -56,7 +98,7 @@
5698
{{/* Use empty data object since title is included in search-data.json */}}
5799
{{ $data = $data | merge (dict "" "") }}
58100
{{ else if (eq $type "summary" ) }}
59-
{{ $data = $data | merge (dict "" ($page.Summary | plainify | htmlUnescape | chomp)) }}
101+
{{ $data = $data | merge (dict "" ($page.Summary | plainify | htmlUnescape | strings.TrimSpace)) }}
60102
{{ end }}
61103

62104
{{ return $data }}

layouts/blog/list.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
<div class="hx:mb-10">
1515
<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>
1616
{{ if site.Params.blog.list.displayTags }}
17-
{{ partial "tags.html" . }}
17+
<div class="hx:text-sm hx:leading-7">
18+
{{ partial "tags.html" (dict "context" .) }}
19+
</div>
1820
{{ end }}
1921
<p class="hx:opacity-80 hx:mt-4 hx:leading-7">{{- partial "utils/page-description" . -}}</p>
2022
<p class="hx:opacity-80 hx:mt-1 hx:leading-7">

0 commit comments

Comments
 (0)